mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-28 08:43:57 +00:00
Compare commits
85 Commits
v1.3.0-bet
...
v1.3.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bad1fc055 | ||
|
|
4569850740 | ||
|
|
54477eabf5 | ||
|
|
43bbc97319 | ||
|
|
cbed2b5cb3 | ||
|
|
8be75545bc | ||
|
|
d748096f7c | ||
|
|
d4ab93c232 | ||
|
|
37a656a292 | ||
|
|
3c94ac1bc1 | ||
|
|
8499dffcd7 | ||
|
|
30a6e85023 | ||
|
|
11530e2720 | ||
|
|
f53466bc7d | ||
|
|
e8ea8ec48f | ||
|
|
6c4c9bdf7e | ||
|
|
e7930a2da0 | ||
|
|
45e1de19dc | ||
|
|
d910bb7928 | ||
|
|
a580c9a44c | ||
|
|
8f5eaefd89 | ||
|
|
7c3a35ae87 | ||
|
|
ea0003f7cb | ||
|
|
3728857c82 | ||
|
|
8e6c49cb37 | ||
|
|
4b31274bda | ||
|
|
429e62d11b | ||
|
|
841a18189a | ||
|
|
59bd066c05 | ||
|
|
efa9dedb85 | ||
|
|
fdffde4dfd | ||
|
|
d08aa7d12c | ||
|
|
f9755a405f | ||
|
|
d751d95bac | ||
|
|
e86eec07e0 | ||
|
|
4abb5c6ced | ||
|
|
32d9a9ec94 | ||
|
|
f6f9ef4ded | ||
|
|
166c93d548 | ||
|
|
8f767068bf | ||
|
|
8d9e2a71e7 | ||
|
|
58b3bca537 | ||
|
|
825f1aaa22 | ||
|
|
82075427e6 | ||
|
|
f89cf673c0 | ||
|
|
ce53f6922f | ||
|
|
b6f70d9a3c | ||
|
|
64d063ccfe | ||
|
|
a98278fb7a | ||
|
|
0553d603e6 | ||
|
|
a36e99308f | ||
|
|
947bac2d35 | ||
|
|
7644cc59cb | ||
|
|
89a441b8ce | ||
|
|
780572c68f | ||
|
|
a13cab65b2 | ||
|
|
e26104adcc | ||
|
|
26ac584655 | ||
|
|
482976990d | ||
|
|
bc4812a12e | ||
|
|
58c2208e2a | ||
|
|
f83d88cfb0 | ||
|
|
8af3dec0df | ||
|
|
edebcc6c59 | ||
|
|
32382ba6be | ||
|
|
46ef6f9df4 | ||
|
|
aea98ff5bf | ||
|
|
c093676575 | ||
|
|
ed05b4b035 | ||
|
|
3aa4412a0f | ||
|
|
ef4b9816e1 | ||
|
|
1c5aab1852 | ||
|
|
966dbc1c74 | ||
|
|
4eafb46c87 | ||
|
|
a97a4d0ed7 | ||
|
|
77c02f9eec | ||
|
|
3157efd421 | ||
|
|
8ff93b33e2 | ||
|
|
c6b9abe4c4 | ||
|
|
150ef6e99e | ||
|
|
0ada407fbe | ||
|
|
c4af1ba643 | ||
|
|
de84421487 | ||
|
|
38a8a7f88a | ||
|
|
b4ddf0e4c3 |
4
.github/workflows/apiserver-test.yaml
vendored
4
.github/workflows/apiserver-test.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
- name: Setup Kind Cluster (Worker)
|
||||
run: |
|
||||
kind delete cluster --name worker
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Setup Kind Cluster (Hub)
|
||||
run: |
|
||||
kind delete cluster
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
|
||||
|
||||
6
.github/workflows/e2e-multicluster-test.yml
vendored
6
.github/workflows/e2e-multicluster-test.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Setup Kind Cluster (Worker)
|
||||
run: |
|
||||
kind delete cluster --name worker
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: Setup Kind Cluster (Hub)
|
||||
run: |
|
||||
kind delete cluster
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: /tmp/e2e-profile.out
|
||||
files: /tmp/e2e-profile.out,/tmp/e2e_multicluster_test.out
|
||||
flags: e2e-multicluster-test
|
||||
name: codecov-umbrella
|
||||
|
||||
|
||||
2
.github/workflows/e2e-rollout-test.yml
vendored
2
.github/workflows/e2e-rollout-test.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Setup Kind Cluster
|
||||
run: |
|
||||
kind delete cluster
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
|
||||
|
||||
2
.github/workflows/e2e-test.yml
vendored
2
.github/workflows/e2e-test.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Setup Kind Cluster
|
||||
run: |
|
||||
kind delete cluster
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
|
||||
kubectl version
|
||||
kubectl cluster-info
|
||||
|
||||
|
||||
9
.github/workflows/go.yml
vendored
9
.github/workflows/go.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Install StaticCheck
|
||||
run: GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck
|
||||
run: GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0
|
||||
|
||||
- name: Static Check
|
||||
run: staticcheck ./...
|
||||
@@ -71,6 +71,11 @@ jobs:
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
@@ -88,7 +93,7 @@ jobs:
|
||||
# version, but we prefer this action because it leaves 'annotations' (i.e.
|
||||
# it comments on PRs to point out linter violations).
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
|
||||
|
||||
41
.github/workflows/registry.yml
vendored
41
.github/workflows/registry.yml
vendored
@@ -44,28 +44,24 @@ jobs:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login Alibaba Cloud ACR
|
||||
- name: Login kubevela private registry
|
||||
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
|
||||
with:
|
||||
driver-opts: image=moby/buildkit:master
|
||||
|
||||
- name: Build & Pushing vela-core for ACR
|
||||
run: |
|
||||
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }} .
|
||||
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- uses: docker/build-push-action@v2
|
||||
name: Build & Pushing vela-core for Dockerhub and GHCR
|
||||
name: Build & Pushing vela-core
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
labels: |-
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -75,19 +71,16 @@ jobs:
|
||||
GOPROXY=https://proxy.golang.org
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository }}/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- name: Build & Pushing vela-apiserver for ACR
|
||||
run: |
|
||||
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }} -f Dockerfile.apiserver .
|
||||
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
- uses: docker/build-push-action@v2
|
||||
name: Build & Pushing vela-apiserver for Dockerhub and GHCR
|
||||
name: Build & Pushing vela-apiserver
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.apiserver
|
||||
labels: |-
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -97,19 +90,16 @@ jobs:
|
||||
GOPROXY=https://proxy.golang.org
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository }}/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- name: Build & Pushing vela runtime rollout for ACR
|
||||
run: |
|
||||
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }} .
|
||||
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
- uses: docker/build-push-action@v2
|
||||
name: Build & Pushing runtime rollout for Dockerhub and GHCR
|
||||
name: Build & Pushing runtime rollout
|
||||
with:
|
||||
context: .
|
||||
file: runtime/rollout/Dockerfile
|
||||
labels: |-
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -119,7 +109,8 @@ jobs:
|
||||
GOPROXY=https://proxy.golang.org
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository }}/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
publish-charts:
|
||||
env:
|
||||
|
||||
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUCKET: ${{ secrets.CLI_OSS_BUCKET }}
|
||||
ENDPOINT: ${{ secrets.CLI_OSS_ENDPOINT }}
|
||||
ACCESS_KEY: ${{ secrets.CLI_OSS_ACCESS_KEY }}
|
||||
ACCESS_KEY_SECRET: ${{ secrets.CLI_OSS_ACCESS_KEY_SECRET }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -104,6 +108,23 @@ jobs:
|
||||
name: sha256sums
|
||||
path: ./_bin/sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt
|
||||
retention-days: 1
|
||||
- name: clear the asset
|
||||
run: |
|
||||
rm -rf ./_bin/vela/${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}
|
||||
mv ./_bin/vela/vela-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz ./_bin/vela/vela-${{ env.VELA_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
|
||||
mv ./_bin/vela/vela-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip ./_bin/vela/vela-${{ env.VELA_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip
|
||||
- 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 local to cloud
|
||||
run: ./ossutil --config-file .ossutilconfig sync ./_bin/vela oss://$BUCKET/binary/vela/${{ env.VELA_VERSION }}
|
||||
|
||||
- name: sync the latest version file
|
||||
run: |
|
||||
echo ${{ env.VELA_VERSION }} > ./latest_version
|
||||
./ossutil --config-file .ossutilconfig cp -u ./latest_version oss://$BUCKET/binary/vela/latest_version
|
||||
|
||||
|
||||
upload-plugin-homebrew:
|
||||
needs: build
|
||||
|
||||
@@ -51,7 +51,8 @@ Full documentation is available on the [KubeVela website](https://kubevela.io/).
|
||||
- Wechat Group (*Chinese*): Broker wechat to add you into the user group.
|
||||
|
||||
<img src="https://static.kubevela.net/images/barnett-wechat.jpg" width="200" />
|
||||
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs)
|
||||
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs).
|
||||
- Bi-weekly Chinese Community Call: [Video Records](https://space.bilibili.com/180074935/channel/seriesdetail?sid=1842207).
|
||||
|
||||
## Talks and Conferences
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ var (
|
||||
// Workflow meta
|
||||
var (
|
||||
WorkflowKind = "Workflow"
|
||||
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
|
||||
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(WorkflowKind)
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/interfaces"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
@@ -121,7 +122,11 @@ func (in ManagedResource) NamespacedName() types.NamespacedName {
|
||||
// ResourceKey computes the key for managed resource, resources with the same key points to the same resource
|
||||
func (in ManagedResource) ResourceKey() string {
|
||||
gv, kind := in.GroupVersionKind().ToAPIVersionAndKind()
|
||||
return strings.Join([]string{gv, kind, in.Cluster, in.Namespace, in.Name}, "/")
|
||||
cluster := in.Cluster
|
||||
if cluster == "" {
|
||||
cluster = velatypes.ClusterLocalName
|
||||
}
|
||||
return strings.Join([]string{gv, kind, cluster, in.Namespace, in.Name}, "/")
|
||||
}
|
||||
|
||||
// ComponentKey computes the key for the component which managed resource belongs to
|
||||
|
||||
@@ -16,9 +16,15 @@ limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
import (
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// ClusterLocalName the name for the hub cluster
|
||||
ClusterLocalName = "local"
|
||||
|
||||
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
|
||||
CredentialTypeInternal v1alpha1.CredentialType = "Internal"
|
||||
// CredentialTypeOCMManagedCluster identifies the virtual cluster from ocm
|
||||
@@ -29,3 +35,8 @@ const (
|
||||
// ClustersArg indicates the argument for specific clusters to install addon
|
||||
ClustersArg = "clusters"
|
||||
)
|
||||
|
||||
var (
|
||||
// AnnotationClusterAlias the annotation key for cluster alias
|
||||
AnnotationClusterAlias = config.MetaApiGroupName + "/cluster-alias"
|
||||
)
|
||||
|
||||
@@ -61,6 +61,22 @@ const (
|
||||
AnnoIngressControllerHTTPSPort = "ingress.controller/https-port"
|
||||
// AnnoIngressControllerHTTPPort define ingress controller listen port for http
|
||||
AnnoIngressControllerHTTPPort = "ingress.controller/http-port"
|
||||
// LabelConfigType is the label for config type
|
||||
LabelConfigType = "config.oam.dev/type"
|
||||
// LabelConfigCatalog is the label for config catalog
|
||||
LabelConfigCatalog = "config.oam.dev/catalog"
|
||||
// LabelConfigSubType is the sub-type for a config type
|
||||
LabelConfigSubType = "config.oam.dev/sub-type"
|
||||
// LabelConfigProject is the label for config project
|
||||
LabelConfigProject = "config.oam.dev/project"
|
||||
// LabelConfigSyncToMultiCluster is the label to decide whether a config will be synchronized to multi-cluster
|
||||
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
|
||||
// LabelConfigIdentifier is the label for config identifier
|
||||
LabelConfigIdentifier = "config.oam.dev/identifier"
|
||||
// AnnotationConfigDescription is the annotation for config description
|
||||
AnnotationConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigAlias is the annotation for config alias
|
||||
AnnotationConfigAlias = "config.oam.dev/alias"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -118,3 +134,29 @@ var DefaultFilterAnnots = []string{
|
||||
oam.AnnotationFilterAnnotationKeys,
|
||||
oam.AnnotationLastAppliedConfiguration,
|
||||
}
|
||||
|
||||
// ConfigType is the type of config
|
||||
type ConfigType string
|
||||
|
||||
const (
|
||||
// TerraformProvider is the config type for terraform provider
|
||||
TerraformProvider = "terraform-provider"
|
||||
// DexConnector is the config type for dex connector
|
||||
DexConnector = "config-dex-connector"
|
||||
// ImageRegistry is the config type for image registry
|
||||
ImageRegistry = "config-image-registry"
|
||||
// HelmRepository is the config type for Helm chart repository
|
||||
HelmRepository = "config-helm-repository"
|
||||
)
|
||||
|
||||
const (
|
||||
// TerraformComponentPrefix is the prefix of component type of terraform-xxx
|
||||
TerraformComponentPrefix = "terraform-"
|
||||
|
||||
// ProviderAppPrefix is the prefix of the application to create a Terraform Provider
|
||||
ProviderAppPrefix = "config-terraform-provider"
|
||||
// ProviderNamespace is the namespace of Terraform Cloud Provider
|
||||
ProviderNamespace = "default"
|
||||
// VelaCoreConfig is to mark application, config and its secret or Terraform provider lelong to a KubeVela config
|
||||
VelaCoreConfig = "velacore-config"
|
||||
)
|
||||
|
||||
@@ -86,7 +86,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
|
||||
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
|
||||
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.0` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.2` |
|
||||
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
|
||||
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
|
||||
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
|
||||
@@ -125,18 +125,20 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
|
||||
|
||||
|
||||
## Uninstalling the Chart
|
||||
## Uninstallation
|
||||
|
||||
To uninstall/delete the KubeVela helm release
|
||||
### Vela CLI
|
||||
|
||||
To uninstall KubeVela, you can just run the following command by vela CLI:
|
||||
|
||||
```shell
|
||||
$ helm uninstall -n vela-system kubevela
|
||||
vela uninstall --force
|
||||
```
|
||||
|
||||
The command removes all the Kubernetes components associated with kubevela and deletes the release.
|
||||
### Helm CLI
|
||||
|
||||
**Notice**: You must disable all the addons before uninstallation, this is a script for convenience.
|
||||
|
||||
**Notice**: If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.Please guarantee there is no application in cluster use this addon and disable it firstly before uninstall the helm chart.
|
||||
You can use this script to disable all addons.
|
||||
```shell
|
||||
#! /bin/sh
|
||||
addon=$(vela addon list|grep enabled|awk {'print $1'})
|
||||
@@ -156,6 +158,15 @@ if [ $fluxcd ]; then
|
||||
fi
|
||||
```
|
||||
|
||||
To uninstall the KubeVela helm release:
|
||||
|
||||
```shell
|
||||
$ helm uninstall -n vela-system kubevela
|
||||
```
|
||||
|
||||
Finally, this command will remove all the Kubernetes resources associated with KubeVela and remove this chart release.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,9 +27,5 @@ Welcome to use the KubeVela! Enjoy your shipping application journey!
|
||||
| . \| |_| || |_) || __/ \ V /| __/| || (_| |
|
||||
|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|
|
||||
|
||||
** Please note before uninstalling **
|
||||
|
||||
If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .
|
||||
Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.
|
||||
Please guarantee there is no application in cluster using this addon and disable it firstly before uninstall the helm chart.
|
||||
And you can find the script of one-short disable all addons from the uninstalling section of https://github.com/oam-dev/kubevela/blob/master/charts/vela-core/README.md.
|
||||
You can refer to https://kubevela.io for more details.
|
||||
|
||||
@@ -106,7 +106,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the commands for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/config-dex-connector.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
custom.definition.oam.dev/alias.config.oam.dev: Dex Connector
|
||||
definition.oam.dev/description: Config information to authenticate Dex connectors
|
||||
labels:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "false"
|
||||
custom.definition.oam.dev/type.config.oam.dev: dex-connector
|
||||
name: config-dex-connector
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "dex-connector"
|
||||
"config.oam.dev/multi-cluster": "false"
|
||||
"config.oam.dev/identifier": parameter.name
|
||||
"config.oam.dev/sub-type": parameter.type
|
||||
}
|
||||
}
|
||||
type: "Opaque"
|
||||
|
||||
if parameter.type == "github" {
|
||||
stringData: parameter.github
|
||||
}
|
||||
if parameter.type == "ldap" {
|
||||
stringData: parameter.ldap
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Config type
|
||||
type: "github" | "ldap"
|
||||
github?: {
|
||||
// +usage=GitHub client ID
|
||||
clientID: string
|
||||
// +usage=GitHub client secret
|
||||
clientSecret: string
|
||||
// +usage=GitHub call back URL
|
||||
callbackURL: string
|
||||
}
|
||||
ldap?: {
|
||||
host: string
|
||||
insecureNoSSL: *true | bool
|
||||
insecureSkipVerify: bool
|
||||
startTLS: bool
|
||||
usernamePrompt: string
|
||||
userSearch: {
|
||||
baseDN: string
|
||||
username: string
|
||||
idAttr: string
|
||||
emailAttr: string
|
||||
nameAttr: string
|
||||
}
|
||||
}
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
@@ -35,20 +36,23 @@ spec:
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
}
|
||||
}
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
stringData: {
|
||||
if parameter.auth != _|_ {
|
||||
".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))
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the container image for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
308
charts/vela-core/templates/defwithtemplate/cron-task.yaml
Normal file
308
charts/vela-core/templates/defwithtemplate/cron-task.yaml
Normal file
@@ -0,0 +1,308 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
|
||||
name: cron-task
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1beta1"
|
||||
kind: "CronJob"
|
||||
spec: {
|
||||
schedule: parameter.schedule
|
||||
concurrencyPolicy: parameter.concurrencyPolicy
|
||||
suspend: parameter.suspend
|
||||
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
|
||||
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
|
||||
if parameter.startingDeadlineSeconds != _|_ {
|
||||
startingDeadlineSeconds: parameter.startingDeadlineSeconds
|
||||
}
|
||||
jobTemplate: {
|
||||
if parameter.labels != _|_ {
|
||||
metadata: labels: parameter.labels
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
metadata: annotations: parameter.annotations
|
||||
}
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
if parameter.ttlSecondsAfterFinished != _|_ {
|
||||
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
|
||||
}
|
||||
if parameter.activeDeadlineSeconds != _|_ {
|
||||
activeDeadlineSeconds: parameter.activeDeadlineSeconds
|
||||
}
|
||||
backoffLimit: parameter.backoffLimit
|
||||
template: {
|
||||
if parameter.labels != _|_ {
|
||||
metadata: labels: parameter.labels
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
metadata: annotations: parameter.annotations
|
||||
}
|
||||
spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
if parameter["imagePullPolicy"] != _|_ {
|
||||
imagePullPolicy: parameter.imagePullPolicy
|
||||
}
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits: cpu: parameter.cpu
|
||||
requests: cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
if parameter["memory"] != _|_ {
|
||||
resources: {
|
||||
limits: memory: parameter.memory
|
||||
requests: memory: parameter.memory
|
||||
}
|
||||
}
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumeMounts: [ for v in parameter.volumes {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
}}]
|
||||
}
|
||||
}]
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumes: [ for v in parameter.volumes {
|
||||
{
|
||||
name: v.name
|
||||
if v.type == "pvc" {
|
||||
persistentVolumeClaim: claimName: v.claimName
|
||||
}
|
||||
if v.type == "configMap" {
|
||||
configMap: {
|
||||
defaultMode: v.defaultMode
|
||||
name: v.cmName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "secret" {
|
||||
secret: {
|
||||
defaultMode: v.defaultMode
|
||||
secretName: v.secretName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "emptyDir" {
|
||||
emptyDir: medium: v.medium
|
||||
}
|
||||
}}]
|
||||
}
|
||||
if parameter["imagePullSecrets"] != _|_ {
|
||||
imagePullSecrets: [ for v in parameter.imagePullSecrets {
|
||||
name: v
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter.hostAliases != _|_ {
|
||||
hostAliases: [ for v in parameter.hostAliases {
|
||||
ip: v.ip
|
||||
hostnames: v.hostnames
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the labels in the workload
|
||||
labels?: [string]: string
|
||||
|
||||
// +usage=Specify the annotations in the workload
|
||||
annotations?: [string]: string
|
||||
|
||||
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
|
||||
schedule: string
|
||||
|
||||
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
|
||||
startingDeadlineSeconds?: int
|
||||
|
||||
// +usage=suspend subsequent executions
|
||||
suspend: *false | bool
|
||||
|
||||
// +usage=Specifies how to treat concurrent executions of a Job
|
||||
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
|
||||
|
||||
// +usage=The number of successful finished jobs to retain
|
||||
successfulJobsHistoryLimit: *3 | int
|
||||
|
||||
// +usage=The number of failed finished jobs to retain
|
||||
failedJobsHistoryLimit: *1 | int
|
||||
|
||||
// +usage=Specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Specify image pull policy for your service
|
||||
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
|
||||
|
||||
// +usage=Specify image pull secrets for your service
|
||||
imagePullSecrets?: [...string]
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
|
||||
// +usage=Define arguments by using environment variables
|
||||
env?: [...{
|
||||
// +usage=Environment variable name
|
||||
name: string
|
||||
// +usage=The value of the environment variable
|
||||
value?: string
|
||||
// +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: {
|
||||
// +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: {
|
||||
// +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
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
|
||||
cpu?: string
|
||||
|
||||
// +usage=Specifies the attributes of the memory resource required for the container.
|
||||
memory?: string
|
||||
|
||||
// +usage=Declare volumes and volumeMounts
|
||||
volumes?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
|
||||
type: "pvc" | "configMap" | "secret" | "emptyDir"
|
||||
if type == "pvc" {
|
||||
claimName: string
|
||||
}
|
||||
if type == "configMap" {
|
||||
defaultMode: *420 | int
|
||||
cmName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "secret" {
|
||||
defaultMode: *420 | int
|
||||
secretName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "emptyDir" {
|
||||
medium: *"" | "Memory"
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
// +usage=Limits the lifetime of a Job that has finished
|
||||
ttlSecondsAfterFinished?: int
|
||||
|
||||
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
|
||||
activeDeadlineSeconds?: int
|
||||
|
||||
// +usage=The number of retries before marking this job failed
|
||||
backoffLimit: *6 | int
|
||||
|
||||
// +usage=Instructions for assessing whether the container is alive.
|
||||
livenessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
|
||||
readinessProbe?: #HealthProbe
|
||||
}
|
||||
#HealthProbe: {
|
||||
|
||||
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
|
||||
exec?: {
|
||||
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
|
||||
command: [...string]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
|
||||
httpGet?: {
|
||||
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
|
||||
path: string
|
||||
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
|
||||
port: int
|
||||
httpHeaders?: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
|
||||
tcpSocket?: {
|
||||
// +usage=The TCP socket within the container that should be probed to assess container health.
|
||||
port: int
|
||||
}
|
||||
|
||||
// +usage=Number of seconds after the container is started before the first probe is initiated.
|
||||
initialDelaySeconds: *0 | int
|
||||
|
||||
// +usage=How often, in seconds, to execute the probe.
|
||||
periodSeconds: *10 | int
|
||||
|
||||
// +usage=Number of seconds after which the probe times out.
|
||||
timeoutSeconds: *1 | int
|
||||
|
||||
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
|
||||
successThreshold: *1 | int
|
||||
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
type: cronjobs.batch
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
if _baseEnv != _|_ {
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar.value}}
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar}}
|
||||
// +patchStrategy=replace
|
||||
env: [ for envVar in _baseEnv if _delKeys[envVar.name] == _|_ && !_params.replace {
|
||||
name: envVar.name
|
||||
@@ -54,7 +54,12 @@ spec:
|
||||
value: _params.env[envVar.name]
|
||||
}
|
||||
if _params.env[envVar.name] == _|_ {
|
||||
value: envVar.value
|
||||
if envVar.value != _|_ {
|
||||
value: envVar.value
|
||||
}
|
||||
if envVar.valueFrom != _|_ {
|
||||
valueFrom: envVar.valueFrom
|
||||
}
|
||||
}
|
||||
}] + [ for k, v in _params.env if _delKeys[k] == _|_ && (_params.replace || _baseEnvMap[k] == _|_) {
|
||||
name: k
|
||||
@@ -92,7 +97,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -132,10 +132,9 @@ spec:
|
||||
parameter.labels
|
||||
}
|
||||
if parameter.addRevisionLabel {
|
||||
"app.oam.dev/appRevision": context.appRevision
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
"app.oam.dev/component": context.name
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
@@ -333,7 +332,7 @@ spec:
|
||||
exposeType: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
|
||||
// +ignore
|
||||
// +usage=If addRevisionLabel is true, the appRevision label will be added to the underlying pods
|
||||
// +usage=If addRevisionLabel is true, the revision label will be added to the underlying pods
|
||||
addRevisionLabel: *false | bool
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
@@ -453,6 +452,12 @@ spec:
|
||||
|
||||
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
}
|
||||
#HealthProbe: {
|
||||
|
||||
@@ -494,12 +499,6 @@ spec:
|
||||
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
|
||||
@@ -104,7 +104,7 @@ multicluster:
|
||||
port: 9443
|
||||
image:
|
||||
repository: oamdev/cluster-gateway
|
||||
tag: v1.3.0
|
||||
tag: v1.3.2
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
limits:
|
||||
|
||||
@@ -105,7 +105,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
|
||||
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
|
||||
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.0` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.2` |
|
||||
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
|
||||
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
|
||||
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,7 +106,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the commands for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/config-dex-connector.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
custom.definition.oam.dev/alias.config.oam.dev: Dex Connector
|
||||
definition.oam.dev/description: Config information to authenticate Dex connectors
|
||||
labels:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "false"
|
||||
custom.definition.oam.dev/type.config.oam.dev: dex-connector
|
||||
name: config-dex-connector
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "dex-connector"
|
||||
"config.oam.dev/multi-cluster": "false"
|
||||
"config.oam.dev/identifier": parameter.name
|
||||
"config.oam.dev/sub-type": parameter.type
|
||||
}
|
||||
}
|
||||
type: "Opaque"
|
||||
|
||||
if parameter.type == "github" {
|
||||
stringData: parameter.github
|
||||
}
|
||||
if parameter.type == "ldap" {
|
||||
stringData: parameter.ldap
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Config type
|
||||
type: "github" | "ldap"
|
||||
github?: {
|
||||
// +usage=GitHub client ID
|
||||
clientID: string
|
||||
// +usage=GitHub client secret
|
||||
clientSecret: string
|
||||
// +usage=GitHub call back URL
|
||||
callbackURL: string
|
||||
}
|
||||
ldap?: {
|
||||
host: string
|
||||
insecureNoSSL: *true | bool
|
||||
insecureSkipVerify: bool
|
||||
startTLS: bool
|
||||
usernamePrompt: string
|
||||
userSearch: {
|
||||
baseDN: string
|
||||
username: string
|
||||
idAttr: string
|
||||
emailAttr: string
|
||||
nameAttr: string
|
||||
}
|
||||
}
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
@@ -35,20 +36,23 @@ spec:
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
}
|
||||
}
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
stringData: {
|
||||
if parameter.auth != _|_ {
|
||||
".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))
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the container image for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
308
charts/vela-minimal/templates/defwithtemplate/cron-task.yaml
Normal file
308
charts/vela-minimal/templates/defwithtemplate/cron-task.yaml
Normal file
@@ -0,0 +1,308 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
|
||||
name: cron-task
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1beta1"
|
||||
kind: "CronJob"
|
||||
spec: {
|
||||
schedule: parameter.schedule
|
||||
concurrencyPolicy: parameter.concurrencyPolicy
|
||||
suspend: parameter.suspend
|
||||
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
|
||||
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
|
||||
if parameter.startingDeadlineSeconds != _|_ {
|
||||
startingDeadlineSeconds: parameter.startingDeadlineSeconds
|
||||
}
|
||||
jobTemplate: {
|
||||
if parameter.labels != _|_ {
|
||||
metadata: labels: parameter.labels
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
metadata: annotations: parameter.annotations
|
||||
}
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
if parameter.ttlSecondsAfterFinished != _|_ {
|
||||
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
|
||||
}
|
||||
if parameter.activeDeadlineSeconds != _|_ {
|
||||
activeDeadlineSeconds: parameter.activeDeadlineSeconds
|
||||
}
|
||||
backoffLimit: parameter.backoffLimit
|
||||
template: {
|
||||
if parameter.labels != _|_ {
|
||||
metadata: labels: parameter.labels
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
metadata: annotations: parameter.annotations
|
||||
}
|
||||
spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
if parameter["imagePullPolicy"] != _|_ {
|
||||
imagePullPolicy: parameter.imagePullPolicy
|
||||
}
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits: cpu: parameter.cpu
|
||||
requests: cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
if parameter["memory"] != _|_ {
|
||||
resources: {
|
||||
limits: memory: parameter.memory
|
||||
requests: memory: parameter.memory
|
||||
}
|
||||
}
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumeMounts: [ for v in parameter.volumes {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
}}]
|
||||
}
|
||||
}]
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumes: [ for v in parameter.volumes {
|
||||
{
|
||||
name: v.name
|
||||
if v.type == "pvc" {
|
||||
persistentVolumeClaim: claimName: v.claimName
|
||||
}
|
||||
if v.type == "configMap" {
|
||||
configMap: {
|
||||
defaultMode: v.defaultMode
|
||||
name: v.cmName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "secret" {
|
||||
secret: {
|
||||
defaultMode: v.defaultMode
|
||||
secretName: v.secretName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "emptyDir" {
|
||||
emptyDir: medium: v.medium
|
||||
}
|
||||
}}]
|
||||
}
|
||||
if parameter["imagePullSecrets"] != _|_ {
|
||||
imagePullSecrets: [ for v in parameter.imagePullSecrets {
|
||||
name: v
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter.hostAliases != _|_ {
|
||||
hostAliases: [ for v in parameter.hostAliases {
|
||||
ip: v.ip
|
||||
hostnames: v.hostnames
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the labels in the workload
|
||||
labels?: [string]: string
|
||||
|
||||
// +usage=Specify the annotations in the workload
|
||||
annotations?: [string]: string
|
||||
|
||||
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
|
||||
schedule: string
|
||||
|
||||
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
|
||||
startingDeadlineSeconds?: int
|
||||
|
||||
// +usage=suspend subsequent executions
|
||||
suspend: *false | bool
|
||||
|
||||
// +usage=Specifies how to treat concurrent executions of a Job
|
||||
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
|
||||
|
||||
// +usage=The number of successful finished jobs to retain
|
||||
successfulJobsHistoryLimit: *3 | int
|
||||
|
||||
// +usage=The number of failed finished jobs to retain
|
||||
failedJobsHistoryLimit: *1 | int
|
||||
|
||||
// +usage=Specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Specify image pull policy for your service
|
||||
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
|
||||
|
||||
// +usage=Specify image pull secrets for your service
|
||||
imagePullSecrets?: [...string]
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
|
||||
// +usage=Define arguments by using environment variables
|
||||
env?: [...{
|
||||
// +usage=Environment variable name
|
||||
name: string
|
||||
// +usage=The value of the environment variable
|
||||
value?: string
|
||||
// +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: {
|
||||
// +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: {
|
||||
// +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
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
|
||||
cpu?: string
|
||||
|
||||
// +usage=Specifies the attributes of the memory resource required for the container.
|
||||
memory?: string
|
||||
|
||||
// +usage=Declare volumes and volumeMounts
|
||||
volumes?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
|
||||
type: "pvc" | "configMap" | "secret" | "emptyDir"
|
||||
if type == "pvc" {
|
||||
claimName: string
|
||||
}
|
||||
if type == "configMap" {
|
||||
defaultMode: *420 | int
|
||||
cmName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "secret" {
|
||||
defaultMode: *420 | int
|
||||
secretName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "emptyDir" {
|
||||
medium: *"" | "Memory"
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
// +usage=Limits the lifetime of a Job that has finished
|
||||
ttlSecondsAfterFinished?: int
|
||||
|
||||
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
|
||||
activeDeadlineSeconds?: int
|
||||
|
||||
// +usage=The number of retries before marking this job failed
|
||||
backoffLimit: *6 | int
|
||||
|
||||
// +usage=Instructions for assessing whether the container is alive.
|
||||
livenessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
|
||||
readinessProbe?: #HealthProbe
|
||||
}
|
||||
#HealthProbe: {
|
||||
|
||||
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
|
||||
exec?: {
|
||||
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
|
||||
command: [...string]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
|
||||
httpGet?: {
|
||||
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
|
||||
path: string
|
||||
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
|
||||
port: int
|
||||
httpHeaders?: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
|
||||
tcpSocket?: {
|
||||
// +usage=The TCP socket within the container that should be probed to assess container health.
|
||||
port: int
|
||||
}
|
||||
|
||||
// +usage=Number of seconds after the container is started before the first probe is initiated.
|
||||
initialDelaySeconds: *0 | int
|
||||
|
||||
// +usage=How often, in seconds, to execute the probe.
|
||||
periodSeconds: *10 | int
|
||||
|
||||
// +usage=Number of seconds after which the probe times out.
|
||||
timeoutSeconds: *1 | int
|
||||
|
||||
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
|
||||
successThreshold: *1 | int
|
||||
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
type: cronjobs.batch
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
if _baseEnv != _|_ {
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar.value}}
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar}}
|
||||
// +patchStrategy=replace
|
||||
env: [ for envVar in _baseEnv if _delKeys[envVar.name] == _|_ && !_params.replace {
|
||||
name: envVar.name
|
||||
@@ -54,7 +54,12 @@ spec:
|
||||
value: _params.env[envVar.name]
|
||||
}
|
||||
if _params.env[envVar.name] == _|_ {
|
||||
value: envVar.value
|
||||
if envVar.value != _|_ {
|
||||
value: envVar.value
|
||||
}
|
||||
if envVar.valueFrom != _|_ {
|
||||
valueFrom: envVar.valueFrom
|
||||
}
|
||||
}
|
||||
}] + [ for k, v in _params.env if _delKeys[k] == _|_ && (_params.replace || _baseEnvMap[k] == _|_) {
|
||||
name: k
|
||||
@@ -92,7 +97,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -132,10 +132,9 @@ spec:
|
||||
parameter.labels
|
||||
}
|
||||
if parameter.addRevisionLabel {
|
||||
"app.oam.dev/appRevision": context.appRevision
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
"app.oam.dev/component": context.name
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
@@ -333,7 +332,7 @@ spec:
|
||||
exposeType: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
|
||||
// +ignore
|
||||
// +usage=If addRevisionLabel is true, the appRevision label will be added to the underlying pods
|
||||
// +usage=If addRevisionLabel is true, the revision label will be added to the underlying pods
|
||||
addRevisionLabel: *false | bool
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
@@ -453,6 +452,12 @@ spec:
|
||||
|
||||
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
}
|
||||
#HealthProbe: {
|
||||
|
||||
@@ -494,12 +499,6 @@ spec:
|
||||
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
|
||||
@@ -107,7 +107,7 @@ multicluster:
|
||||
port: 9443
|
||||
image:
|
||||
repository: oamdev/cluster-gateway
|
||||
tag: v1.3.0
|
||||
tag: v1.3.2
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
limits:
|
||||
|
||||
@@ -19,7 +19,6 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -30,6 +29,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -147,6 +148,7 @@ func main() {
|
||||
flag.IntVar(&workflow.MaxWorkflowWaitBackoffTime, "max-workflow-wait-backoff-time", 60, "Set the max workflow wait backoff time, default is 60")
|
||||
flag.IntVar(&workflow.MaxWorkflowFailedBackoffTime, "max-workflow-failed-backoff-time", 300, "Set the max workflow wait backoff time, default is 300")
|
||||
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
|
||||
|
||||
@@ -7,4 +7,5 @@ coverage:
|
||||
default:
|
||||
target: 70%
|
||||
ignore:
|
||||
- "**/zz_generated.deepcopy.go"
|
||||
- "**/zz_generated.deepcopy.go"
|
||||
- "references/"
|
||||
|
||||
@@ -6,3 +6,4 @@ This directory contains guides for contributors to the KubeVela project.
|
||||
* [Developer guide](./developer-guide.md)
|
||||
* [Triage issues](./triage-issues.md)
|
||||
* [Code conventions](./coding-conventions.md)
|
||||
* [Develop Code Flow](./develop-code-flow.pdf)
|
||||
|
||||
BIN
contribute/develop-code-flow.pdf
Normal file
BIN
contribute/develop-code-flow.pdf
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@ This guide helps you get started developing KubeVela.
|
||||
## Prerequisites
|
||||
|
||||
1. Golang version 1.17+
|
||||
2. Kubernetes version v1.18+ with `~/.kube/config` configured.
|
||||
2. Kubernetes version v1.20+ with `~/.kube/config` configured.
|
||||
3. ginkgo 1.14.0+ (just for [E2E test](./developer-guide.md#e2e-test))
|
||||
4. golangci-lint 1.38.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
|
||||
5. kubebuilder v3.1.0+ and you need to manually install the dependency tools for unit test.
|
||||
@@ -177,7 +177,7 @@ To execute the e2e test of the API module, the mongodb service needs to exist lo
|
||||
# save your config
|
||||
mv ~/.kube/config ~/.kube/config.save
|
||||
|
||||
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
|
||||
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
|
||||
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
|
||||
kind get kubeconfig --name worker > /tmp/worker.client.kubeconfig
|
||||
|
||||
|
||||
@@ -3337,6 +3337,284 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/config_types": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "list all config types",
|
||||
"operationId": "listConfigTypes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Fuzzy search based on name and description.",
|
||||
"name": "query",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1.ConfigType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/config_types/{configType}": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "get a config type",
|
||||
"operationId": "getConfigType",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config type",
|
||||
"name": "configType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ConfigType"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "create or update a config",
|
||||
"operationId": "createConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config type",
|
||||
"name": "configType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.CreateConfigRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EmptyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/config_types/{configType}/configs": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "get configs from a config type",
|
||||
"operationId": "getConfigs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config",
|
||||
"name": "configType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/*v1.Config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/config_types/{configType}/configs/{name}": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "get a config from a config type",
|
||||
"operationId": "getConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config type",
|
||||
"name": "configType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/*v1.Config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"config"
|
||||
],
|
||||
"summary": "delete a config",
|
||||
"operationId": "deleteConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config type",
|
||||
"name": "configType",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the config",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EmptyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/definitions": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
@@ -3862,6 +4140,55 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/projects/{projectName}/configs": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"project"
|
||||
],
|
||||
"summary": "get configs which are in a project",
|
||||
"operationId": "getConfigs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "config type",
|
||||
"name": "configType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "identifier of the project",
|
||||
"name": "projectName",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/*v1.Config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/projects/{projectName}/permissions": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
@@ -4304,6 +4631,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/repository/chart_repos": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/xml",
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/xml"
|
||||
],
|
||||
"tags": [
|
||||
"repository",
|
||||
"helm"
|
||||
],
|
||||
"summary": "list chart repo",
|
||||
"operationId": "listRepo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the config project",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/bcode.Bcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/repository/charts": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
@@ -4326,6 +4696,12 @@
|
||||
"description": "helm repository url",
|
||||
"name": "repoUrl",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "secret of the repo",
|
||||
"name": "secretName",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4369,6 +4745,12 @@
|
||||
"description": "helm repository url",
|
||||
"name": "repoUrl",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "secret of the repo",
|
||||
"name": "secretName",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4409,6 +4791,12 @@
|
||||
"description": "helm repository url",
|
||||
"name": "repoUrl",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "secret of the repo",
|
||||
"name": "secretName",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5241,6 +5629,7 @@
|
||||
},
|
||||
"definitions": {
|
||||
"*v1.ApplicationTriggerBase": {},
|
||||
"*v1.Config": {},
|
||||
"*v1.EmptyResponse": {},
|
||||
"addon.Dependency": {
|
||||
"properties": {
|
||||
@@ -5293,6 +5682,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"addon.GitlabAddonSource": {
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addon.HelmSource": {
|
||||
"properties": {
|
||||
"url": {
|
||||
@@ -6562,34 +6967,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.SystemInfo": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"installID",
|
||||
"enableCollection",
|
||||
"loginType"
|
||||
],
|
||||
"properties": {
|
||||
"createTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"enableCollection": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"installID": {
|
||||
"type": "string"
|
||||
},
|
||||
"loginType": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.WorkflowStepStatus": {
|
||||
"required": [
|
||||
"id",
|
||||
@@ -6978,6 +7355,9 @@
|
||||
"gitee": {
|
||||
"$ref": "#/definitions/addon.GiteeAddonSource"
|
||||
},
|
||||
"gitlab": {
|
||||
"$ref": "#/definitions/addon.GitlabAddonSource"
|
||||
},
|
||||
"helm": {
|
||||
"$ref": "#/definitions/addon.HelmSource"
|
||||
},
|
||||
@@ -7148,12 +7528,12 @@
|
||||
},
|
||||
"v1.ApplicationDeployResponse": {
|
||||
"required": [
|
||||
"note",
|
||||
"envName",
|
||||
"triggerType",
|
||||
"createTime",
|
||||
"version",
|
||||
"note",
|
||||
"status",
|
||||
"envName",
|
||||
"triggerType"
|
||||
"status"
|
||||
],
|
||||
"properties": {
|
||||
"codeInfo": {
|
||||
@@ -7687,6 +8067,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ConfigType": {
|
||||
"required": [
|
||||
"definitions",
|
||||
"alias",
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"definitions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ConnectCloudClusterRequest": {
|
||||
"required": [
|
||||
"accessKeyID",
|
||||
@@ -7736,6 +8141,9 @@
|
||||
"gitee": {
|
||||
"$ref": "#/definitions/addon.GiteeAddonSource"
|
||||
},
|
||||
"gitlab": {
|
||||
"$ref": "#/definitions/addon.GitlabAddonSource"
|
||||
},
|
||||
"helm": {
|
||||
"$ref": "#/definitions/addon.HelmSource"
|
||||
},
|
||||
@@ -8032,6 +8440,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.CreateConfigRequest": {
|
||||
"required": [
|
||||
"name",
|
||||
"alias",
|
||||
"project",
|
||||
"componentType"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"componentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.CreateEnvRequest": {
|
||||
"required": [
|
||||
"name",
|
||||
@@ -8243,11 +8676,11 @@
|
||||
},
|
||||
"v1.DetailAddonResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"icon",
|
||||
"invisible",
|
||||
"version",
|
||||
"description",
|
||||
"invisible",
|
||||
"name",
|
||||
"icon",
|
||||
"schema",
|
||||
"uiSchema",
|
||||
"definitions",
|
||||
@@ -8327,13 +8760,13 @@
|
||||
},
|
||||
"v1.DetailApplicationResponse": {
|
||||
"required": [
|
||||
"alias",
|
||||
"icon",
|
||||
"project",
|
||||
"description",
|
||||
"createTime",
|
||||
"name",
|
||||
"project",
|
||||
"alias",
|
||||
"updateTime",
|
||||
"icon",
|
||||
"policies",
|
||||
"envBindings",
|
||||
"applicationType",
|
||||
@@ -8394,19 +8827,19 @@
|
||||
},
|
||||
"v1.DetailClusterResponse": {
|
||||
"required": [
|
||||
"updateTime",
|
||||
"kubeConfig",
|
||||
"name",
|
||||
"icon",
|
||||
"reason",
|
||||
"provider",
|
||||
"apiServerURL",
|
||||
"name",
|
||||
"alias",
|
||||
"icon",
|
||||
"dashboardURL",
|
||||
"createTime",
|
||||
"alias",
|
||||
"description",
|
||||
"labels",
|
||||
"status",
|
||||
"updateTime",
|
||||
"apiServerURL",
|
||||
"kubeConfig",
|
||||
"labels",
|
||||
"kubeConfigSecret",
|
||||
"resourceInfo"
|
||||
],
|
||||
@@ -8465,14 +8898,14 @@
|
||||
},
|
||||
"v1.DetailComponentResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"updateTime",
|
||||
"appPrimaryKey",
|
||||
"createTime",
|
||||
"creator",
|
||||
"alias",
|
||||
"name",
|
||||
"main",
|
||||
"createTime",
|
||||
"type",
|
||||
"main",
|
||||
"appPrimaryKey",
|
||||
"definition"
|
||||
],
|
||||
"properties": {
|
||||
@@ -8574,13 +9007,13 @@
|
||||
},
|
||||
"v1.DetailPolicyResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"description",
|
||||
"creator",
|
||||
"properties",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"name",
|
||||
"type"
|
||||
"updateTime"
|
||||
],
|
||||
"properties": {
|
||||
"createTime": {
|
||||
@@ -8610,17 +9043,17 @@
|
||||
},
|
||||
"v1.DetailRevisionResponse": {
|
||||
"required": [
|
||||
"envName",
|
||||
"reason",
|
||||
"deployUser",
|
||||
"updateTime",
|
||||
"status",
|
||||
"note",
|
||||
"triggerType",
|
||||
"workflowName",
|
||||
"updateTime",
|
||||
"version",
|
||||
"reason",
|
||||
"createTime",
|
||||
"appPrimaryKey",
|
||||
"version"
|
||||
"status",
|
||||
"deployUser",
|
||||
"note",
|
||||
"workflowName",
|
||||
"envName",
|
||||
"appPrimaryKey"
|
||||
],
|
||||
"properties": {
|
||||
"appPrimaryKey": {
|
||||
@@ -8674,10 +9107,10 @@
|
||||
},
|
||||
"v1.DetailTargetResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"createTime",
|
||||
"project",
|
||||
"updateTime"
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
@@ -8717,11 +9150,11 @@
|
||||
},
|
||||
"v1.DetailUserResponse": {
|
||||
"required": [
|
||||
"lastLoginTime",
|
||||
"name",
|
||||
"email",
|
||||
"disabled",
|
||||
"createTime",
|
||||
"lastLoginTime",
|
||||
"projects",
|
||||
"roles"
|
||||
],
|
||||
@@ -8762,12 +9195,12 @@
|
||||
},
|
||||
"v1.DetailWorkflowRecordResponse": {
|
||||
"required": [
|
||||
"name",
|
||||
"namespace",
|
||||
"workflowName",
|
||||
"workflowAlias",
|
||||
"applicationRevision",
|
||||
"status",
|
||||
"name",
|
||||
"namespace",
|
||||
"deployTime",
|
||||
"deployUser",
|
||||
"note",
|
||||
@@ -8819,14 +9252,14 @@
|
||||
},
|
||||
"v1.DetailWorkflowResponse": {
|
||||
"required": [
|
||||
"description",
|
||||
"enable",
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"name",
|
||||
"alias",
|
||||
"enable",
|
||||
"default",
|
||||
"envName"
|
||||
"envName",
|
||||
"createTime",
|
||||
"alias",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
@@ -9021,8 +9454,8 @@
|
||||
},
|
||||
"v1.EnvBindingTarget": {
|
||||
"required": [
|
||||
"name",
|
||||
"alias"
|
||||
"alias",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"alias": {
|
||||
@@ -9405,11 +9838,11 @@
|
||||
},
|
||||
"v1.LoginUserInfoResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"lastLoginTime",
|
||||
"name",
|
||||
"email",
|
||||
"disabled",
|
||||
"createTime",
|
||||
"lastLoginTime",
|
||||
"projects",
|
||||
"platformPermissions",
|
||||
"projectPermissions"
|
||||
@@ -9717,6 +10150,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.SystemInfo": {
|
||||
"required": [
|
||||
"installID",
|
||||
"enableCollection",
|
||||
"loginType"
|
||||
],
|
||||
"properties": {
|
||||
"enableCollection": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"installID": {
|
||||
"type": "string"
|
||||
},
|
||||
"loginType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.SystemInfoRequest": {
|
||||
"required": [
|
||||
"enableCollection",
|
||||
@@ -9728,23 +10179,20 @@
|
||||
},
|
||||
"loginType": {
|
||||
"type": "string"
|
||||
},
|
||||
"velaAddress": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.SystemInfoResponse": {
|
||||
"required": [
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"installID",
|
||||
"enableCollection",
|
||||
"loginType",
|
||||
"systemVersion"
|
||||
],
|
||||
"properties": {
|
||||
"createTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"enableCollection": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -9756,10 +10204,6 @@
|
||||
},
|
||||
"systemVersion": {
|
||||
"$ref": "#/definitions/v1.SystemVersion"
|
||||
},
|
||||
"updateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9828,6 +10272,9 @@
|
||||
"gitee": {
|
||||
"$ref": "#/definitions/addon.GiteeAddonSource"
|
||||
},
|
||||
"gitlab": {
|
||||
"$ref": "#/definitions/addon.GitlabAddonSource"
|
||||
},
|
||||
"helm": {
|
||||
"$ref": "#/definitions/addon.HelmSource"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@ kind: Application
|
||||
metadata:
|
||||
name: config-dex-connector-dev
|
||||
namespace: vela-system
|
||||
labels:
|
||||
"app.oam.dev/source-of-truth": "from-inner-system"
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "config-dex-connector"
|
||||
"config.oam.dev/sub-type": "github"
|
||||
project: abc
|
||||
spec:
|
||||
components:
|
||||
- name: dev
|
||||
@@ -12,4 +18,4 @@ spec:
|
||||
github:
|
||||
clientID: "aa"
|
||||
clientSecret: "bb"
|
||||
callbackURL: "http://localhost:8080/callback"
|
||||
redirectURI: "http://localhost:8080/callback"
|
||||
@@ -1,11 +1,16 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: config-image-registry-account-auth-dev
|
||||
name: image-dev
|
||||
namespace: vela-system
|
||||
labels:
|
||||
"app.oam.dev/source-of-truth": "from-inner-system"
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "config-image-registry"
|
||||
project: abc
|
||||
spec:
|
||||
components:
|
||||
- name: account-auth
|
||||
- name: image-dev
|
||||
type: config-image-registry
|
||||
properties:
|
||||
registry: "registry.cn-beijing.aliyuncs.com"
|
||||
|
||||
@@ -12,12 +12,11 @@ spec:
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
name: image-registry-dev
|
||||
- name: reg-demo
|
||||
resource: secret
|
||||
policies:
|
||||
- type: topology
|
||||
name: dev
|
||||
properties:
|
||||
clusters: ["bj"]
|
||||
# namespaces: ["ns1"]
|
||||
namespace: default
|
||||
|
||||
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: mock-addon
|
||||
version: 1.0.0
|
||||
description: Extended workload to do continuous and progressive delivery
|
||||
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
|
||||
url: https://fluxcd.io
|
||||
|
||||
tags:
|
||||
- extended_workload
|
||||
- gitops
|
||||
- only_example
|
||||
|
||||
deployTo:
|
||||
control_plane: true
|
||||
runtime_cluster: false
|
||||
|
||||
dependencies: []
|
||||
#- name: addon_name
|
||||
|
||||
# set invisible means this won't be list and will be enabled when depended on
|
||||
# for example, terraform-alibaba depends on terraform which is invisible,
|
||||
# when terraform-alibaba is enabled, terraform will be enabled automatically
|
||||
# default: false
|
||||
invisible: false
|
||||
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mock-addon
|
||||
namespace: vela-system
|
||||
spec:
|
||||
components:
|
||||
- name: ns-example-system
|
||||
type: raw
|
||||
properties:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: mock-system
|
||||
21
go.mod
21
go.mod
@@ -63,10 +63,11 @@ require (
|
||||
github.com/wonderflow/cert-manager-api v1.0.3
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.18.1
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
@@ -96,6 +97,8 @@ require (
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
require github.com/robfig/cron/v3 v3.0.1
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
@@ -105,7 +108,7 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
@@ -145,6 +148,7 @@ require (
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/emicklei/proto v1.6.15 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
@@ -247,11 +251,10 @@ require (
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
|
||||
40
go.sum
40
go.sum
@@ -110,8 +110,9 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
@@ -420,8 +421,9 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
@@ -1436,6 +1438,8 @@ github.com/rancher/wrangler v0.4.0/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -1653,7 +1657,7 @@ github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||
@@ -1795,8 +1799,9 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1840,8 +1845,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1906,9 +1912,10 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1921,8 +1928,9 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -2045,14 +2053,15 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2063,8 +2072,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -2199,8 +2209,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6 h1:SIasE1FVIQOWz2GEAHFOmoW7xchJcqlucjSULTL0Ag4=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -25,7 +25,7 @@ ifeq (, $(shell which staticcheck))
|
||||
@{ \
|
||||
set -e ;\
|
||||
echo 'installing honnef.co/go/tools/cmd/staticcheck ' ;\
|
||||
GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck ;\
|
||||
GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0 ;\
|
||||
}
|
||||
STATICCHECK=$(GOBIN)/staticcheck
|
||||
else
|
||||
@@ -56,7 +56,7 @@ else
|
||||
CUE=$(shell which cue)
|
||||
endif
|
||||
|
||||
KUSTOMIZE_VERSION ?= 3.8.2
|
||||
KUSTOMIZE_VERSION ?= 4.5.4
|
||||
|
||||
.PHONY: kustomize
|
||||
kustomize:
|
||||
|
||||
@@ -49,6 +49,7 @@ e2e-apiserver-test:
|
||||
pkill vela_addon_mock_server || true
|
||||
go run ./e2e/addon/mock/vela_addon_mock_server.go &
|
||||
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
|
||||
sleep 15
|
||||
@$(OK) tests pass
|
||||
|
||||
.PHONY: e2e-test
|
||||
|
||||
@@ -195,6 +195,10 @@ func GetPatternFromItem(it Item, r AsyncReader, rootPath string) string {
|
||||
if strings.HasPrefix(relativePath, strings.Join([]string{rootPath, p.Value}, "/")) {
|
||||
return p.Value
|
||||
}
|
||||
if strings.HasPrefix(relativePath, filepath.Join(rootPath, p.Value)) {
|
||||
// for enable addon by load dir, compatible with linux or windows os
|
||||
return p.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -357,7 +361,7 @@ func readResFile(a *InstallPackage, reader AsyncReader, readPath string) error {
|
||||
if filename == "parameter.cue" {
|
||||
return nil
|
||||
}
|
||||
file := ElementFile{Data: b, Name: path.Base(readPath)}
|
||||
file := ElementFile{Data: b, Name: filepath.Base(readPath)}
|
||||
switch filepath.Ext(filename) {
|
||||
case ".cue":
|
||||
a.CUETemplates = append(a.CUETemplates, file)
|
||||
@@ -375,7 +379,7 @@ func readDefSchemaFile(a *InstallPackage, reader AsyncReader, readPath string) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.DefSchemas = append(a.DefSchemas, ElementFile{Data: b, Name: path.Base(readPath)})
|
||||
a.DefSchemas = append(a.DefSchemas, ElementFile{Data: b, Name: filepath.Base(readPath)})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -386,7 +390,7 @@ func readDefFile(a *UIData, reader AsyncReader, readPath string) error {
|
||||
return err
|
||||
}
|
||||
filename := path.Base(readPath)
|
||||
file := ElementFile{Data: b, Name: path.Base(readPath)}
|
||||
file := ElementFile{Data: b, Name: filepath.Base(readPath)}
|
||||
switch filepath.Ext(filename) {
|
||||
case ".cue":
|
||||
a.CUEDefinitions = append(a.CUEDefinitions, file)
|
||||
@@ -1070,7 +1074,7 @@ func (h *Installer) enableAddon(addon *InstallPackage) error {
|
||||
h.addon = addon
|
||||
err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
|
||||
if err != nil {
|
||||
return ErrVersionMismatch
|
||||
return VersionUnMatchError{addonName: addon.Name, err: err}
|
||||
}
|
||||
|
||||
if err = h.installDependency(addon); err != nil {
|
||||
@@ -1188,6 +1192,8 @@ func (h *Installer) createOrUpdate(app *v1beta1.Application) error {
|
||||
return err
|
||||
}
|
||||
getapp.Spec = app.Spec
|
||||
getapp.Labels = app.Labels
|
||||
getapp.Annotations = app.Annotations
|
||||
err = h.cli.Update(h.ctx, &getapp)
|
||||
if err != nil {
|
||||
klog.Errorf("fail to create application: %v", err)
|
||||
@@ -1342,7 +1348,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
|
||||
return err
|
||||
}
|
||||
if !res {
|
||||
return fmt.Errorf("vela cli/ux version: %s cannot meet requirement", version2.VelaVersion)
|
||||
return fmt.Errorf("vela cli/ux version: %s require: %s", version2.VelaVersion, require.VelaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1359,7 +1365,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
|
||||
return err
|
||||
}
|
||||
if !res {
|
||||
return fmt.Errorf("the vela core controller: %s cannot meet requirement ", imageVersion)
|
||||
return fmt.Errorf("the vela core controller: %s require: %s", imageVersion, require.VelaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1380,7 +1386,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
|
||||
}
|
||||
|
||||
if !res {
|
||||
return fmt.Errorf("the kubernetes version %s cannot meet requirement", k8sVersion.GitVersion)
|
||||
return fmt.Errorf("the kubernetes version %s require: %s", k8sVersion.GitVersion, require.KubernetesVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -323,6 +325,60 @@ var _ = Describe("Test render addon with specified clusters", func() {
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("func addon update ", func() {
|
||||
It("test update addon app label", func() {
|
||||
app_test_update := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(addonUpdateAppYaml), &app_test_update)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &app_test_update)).Should(BeNil())
|
||||
|
||||
Eventually(func() error {
|
||||
var err error
|
||||
appCheck := v1beta1.Application{}
|
||||
err = k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-update"}, &appCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if appCheck.Labels["addons.oam.dev/version"] != "v1.2.0" {
|
||||
return fmt.Errorf("label missmatch")
|
||||
}
|
||||
return nil
|
||||
}, time.Millisecond*500, 30*time.Second).Should(BeNil())
|
||||
|
||||
pkg := &InstallPackage{Meta: Meta{Name: "test-update", Version: "1.3.0"}}
|
||||
h := NewAddonInstaller(context.Background(), k8sClient, nil, nil, nil, &Registry{Name: "test"}, nil, nil)
|
||||
h.addon = pkg
|
||||
Expect(h.dispatchAddonResource(pkg)).Should(BeNil())
|
||||
|
||||
Eventually(func() error {
|
||||
var err error
|
||||
appCheck := v1beta1.Application{}
|
||||
err = k8sClient.Get(context.Background(), types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-update"}, &appCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if appCheck.Labels["addons.oam.dev/version"] != "1.3.0" {
|
||||
return fmt.Errorf("label missmatch")
|
||||
}
|
||||
return nil
|
||||
}, time.Second*3, 300*time.Second).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("test enable addon in local dir", func() {
|
||||
BeforeEach(func() {
|
||||
app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-example"}}
|
||||
Expect(k8sClient.Delete(ctx, &app)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{}))
|
||||
})
|
||||
|
||||
It("test enable addon by local dir", func() {
|
||||
ctx := context.Background()
|
||||
err := EnableAddonByLocalDir(ctx, "example", "./testdata/example", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"})
|
||||
Expect(err).Should(BeNil())
|
||||
app := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-example"}, &app)).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
const (
|
||||
appYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
@@ -400,4 +456,21 @@ spec:
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30`
|
||||
|
||||
addonUpdateAppYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: addon-test-update
|
||||
namespace: vela-system
|
||||
labels:
|
||||
addons.oam.dev/version: v1.2.0
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
`
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
v1alpha12 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -303,7 +304,7 @@ func TestGetAddonStatus(t *testing.T) {
|
||||
getFunc := test.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
switch key.Name {
|
||||
case "addon-disabled", "disabled":
|
||||
return errors.NewNotFound(schema.GroupResource{Group: "apiVersion: core.oam.dev/v1beta1", Resource: "app"}, key.Name)
|
||||
return kerrors.NewNotFound(schema.GroupResource{Group: "apiVersion: core.oam.dev/v1beta1", Resource: "app"}, key.Name)
|
||||
case "addon-suspend":
|
||||
o := obj.(*v1beta1.Application)
|
||||
app := &v1beta1.Application{}
|
||||
@@ -897,3 +898,11 @@ func TestRenderCUETemplate(t *testing.T) {
|
||||
assert.True(t, component.Type == "raw")
|
||||
assert.True(t, config["metadata"].(map[string]interface{})["labels"].(map[string]interface{})["version"] == "1.0.1")
|
||||
}
|
||||
|
||||
func TestCheckEnableAddonErrorWhenMissMatch(t *testing.T) {
|
||||
version2.VelaVersion = "v1.3.0"
|
||||
i := InstallPackage{Meta: Meta{SystemRequirements: &SystemRequirements{VelaVersion: ">=1.4.0"}}}
|
||||
installer := &Installer{}
|
||||
err := installer.enableAddon(&i)
|
||||
assert.Equal(t, errors.As(err, &VersionUnMatchError{}), true)
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ package addon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
// We have three addon layer here
|
||||
@@ -106,7 +108,7 @@ func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", utils.Sanitize(r.Name), err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -117,28 +119,27 @@ func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error
|
||||
// ListUIData will always list UIData from cache first, if not exist, read from source.
|
||||
func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
|
||||
var err error
|
||||
listAddons := u.listCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
var listAddons []*UIData
|
||||
if !IsVersionRegistry(r) {
|
||||
addonMeta, err := u.ListAddonMeta(r)
|
||||
listAddons = u.listCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
listAddons, err = u.listUIDataAndCache(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
listAddons, err = versionedRegistry.ListAddon()
|
||||
listAddons = u.listVersionRegistryCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
listAddons, err = u.listVersionRegistryUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, listAddons)
|
||||
|
||||
return listAddons, nil
|
||||
}
|
||||
|
||||
@@ -173,6 +174,27 @@ func (u *Cache) listCachedUIData(name string) []*UIData {
|
||||
return d
|
||||
}
|
||||
|
||||
// listVersionRegistryCachedUIData will get cached addons from specified VersionRegistry in cache
|
||||
func (u *Cache) listVersionRegistryCachedUIData(name string) []*UIData {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
d, ok := u.versionedUIData[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var uiDatas []*UIData
|
||||
for version, uiData := range d {
|
||||
if !strings.Contains(version, "-latest") {
|
||||
uiDatas = append(uiDatas, uiData)
|
||||
}
|
||||
}
|
||||
|
||||
return uiDatas
|
||||
}
|
||||
|
||||
// getCachedAddonMeta will get cached registry meta from specified registry in cache
|
||||
func (u *Cache) getCachedAddonMeta(name string) map[string]SourceMeta {
|
||||
if u == nil {
|
||||
@@ -258,35 +280,51 @@ func (u *Cache) discoverAndRefreshRegistry() {
|
||||
|
||||
for _, r := range registries {
|
||||
if !IsVersionRegistry(r) {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
_, err = u.listUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
uiDatas, err := versionedRegistry.ListAddon()
|
||||
_, err = u.listVersionRegistryUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
for _, addon := range uiDatas {
|
||||
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addon from registry %s, addon %s version %s for cache updating, %v", addon.Name, r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
|
||||
// we also no version key, if use get addonUIData without version will return this vale as latest data.
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Cache) listUIDataAndCache(r Registry) ([]*UIData, error) {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
return uiData, nil
|
||||
}
|
||||
|
||||
func (u *Cache) listVersionRegistryUIDataAndCache(r Registry) ([]*UIData, error) {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
uiDatas, err := versionedRegistry.ListAddon()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, addon := range uiDatas {
|
||||
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addon from versioned registry %s, addon %s version %s for cache updating, %v", r.Name, addon.Name, addon.Version, err)
|
||||
continue
|
||||
}
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
|
||||
// we also no version key, if use get addonUIData without version will return this vale as latest data.
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
|
||||
}
|
||||
return uiDatas, nil
|
||||
}
|
||||
|
||||
@@ -31,3 +31,63 @@ func TestPutVersionedUIData2cache(t *testing.T) {
|
||||
assert.NotEmpty(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"])
|
||||
assert.Equal(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"].Name, "fluxcd")
|
||||
}
|
||||
|
||||
func TestPutAddonUIData2Cache(t *testing.T) {
|
||||
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
|
||||
addons := []*UIData{&uiData}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonUIData2Cache(name, addons)
|
||||
assert.NotEmpty(t, u.uiData)
|
||||
assert.Equal(t, u.uiData[name], addons)
|
||||
}
|
||||
|
||||
func TestListCachedUIData(t *testing.T) {
|
||||
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
|
||||
addons := []*UIData{&uiData}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonUIData2Cache(name, addons)
|
||||
|
||||
assert.Equal(t, u.listCachedUIData(name), addons)
|
||||
}
|
||||
|
||||
func TestPutAddonMeta2Cache(t *testing.T) {
|
||||
addonMeta := map[string]SourceMeta{
|
||||
"fluxcd": {
|
||||
Name: "fluxcd",
|
||||
Items: []Item{
|
||||
&OSSItem{
|
||||
tp: FileType,
|
||||
path: "fluxcd/definitions/helm-release.yaml",
|
||||
name: "helm-release.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonMeta2Cache(name, addonMeta)
|
||||
assert.NotEmpty(t, u.registryMeta)
|
||||
assert.Equal(t, u.registryMeta[name], addonMeta)
|
||||
}
|
||||
|
||||
func TestGetCachedAddonMeta(t *testing.T) {
|
||||
addonMeta := map[string]SourceMeta{
|
||||
"fluxcd": {
|
||||
Name: "fluxcd",
|
||||
Items: []Item{
|
||||
&OSSItem{
|
||||
tp: FileType,
|
||||
path: "fluxcd/definitions/helm-release.yaml",
|
||||
name: "helm-release.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonMeta2Cache(name, addonMeta)
|
||||
|
||||
assert.Equal(t, u.getCachedAddonMeta(name), addonMeta)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package addon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-github/v32/github"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -35,9 +37,6 @@ var (
|
||||
|
||||
// ErrNotExist means addon not exists
|
||||
ErrNotExist = NewAddonError("addon not exist")
|
||||
|
||||
// ErrVersionMismatch means addon version requirement mismatch
|
||||
ErrVersionMismatch = NewAddonError("addon version requirements mismatch")
|
||||
)
|
||||
|
||||
// WrapErrRateLimit return ErrRateLimit if is the situation, or return error directly
|
||||
@@ -48,3 +47,13 @@ func WrapErrRateLimit(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// VersionUnMatchError means addon system requirement cannot meet requirement
|
||||
type VersionUnMatchError struct {
|
||||
err error
|
||||
addonName string
|
||||
}
|
||||
|
||||
func (v VersionUnMatchError) Error() string {
|
||||
return fmt.Sprintf("addon %s system requirement miss match: %v", v.addonName, v.err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -92,7 +93,11 @@ 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 {
|
||||
r := localReader{dir: dir, name: name}
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := localReader{dir: absDir, name: name}
|
||||
metas, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,8 +37,10 @@ func (l localReader) ListAddonMeta() (map[string]SourceMeta, error) {
|
||||
}
|
||||
|
||||
func (l localReader) ReadFile(path string) (string, error) {
|
||||
file := strings.TrimPrefix(path, l.name+"/")
|
||||
b, err := ioutil.ReadFile(filepath.Clean(filepath.Join(l.dir, file)))
|
||||
path = strings.TrimPrefix(path, l.name+"/")
|
||||
// for windows
|
||||
path = strings.TrimPrefix(path, l.name+"\\")
|
||||
b, err := ioutil.ReadFile(filepath.Clean(filepath.Join(l.dir, path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -46,7 +48,8 @@ func (l localReader) ReadFile(path string) (string, error) {
|
||||
}
|
||||
|
||||
func (l localReader) RelativePath(item Item) string {
|
||||
return filepath.Join(l.name, strings.TrimPrefix(item.GetPath()+"/", l.dir))
|
||||
file := strings.TrimPrefix(item.GetPath(), filepath.Clean(l.dir))
|
||||
return filepath.Join(l.name, file)
|
||||
}
|
||||
|
||||
func recursiveFetchFiles(path string, metas *SourceMeta) error {
|
||||
@@ -60,7 +63,7 @@ func recursiveFetchFiles(path string, metas *SourceMeta) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
metas.Items = append(metas.Items, OSSItem{tp: "file", path: fmt.Sprintf("%s/%s", path, file.Name()), name: file.Name()})
|
||||
metas.Items = append(metas.Items, OSSItem{tp: "file", path: filepath.Join(path, file.Name()), name: file.Name()})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -68,6 +68,43 @@ type HelmSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
}
|
||||
|
||||
// SafeCopier is an interface to copy Struct without sensitive fields, such as Token, Username, Password
|
||||
type SafeCopier interface {
|
||||
SafeCopy() interface{}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GitAddonSource) SafeCopy() *GitAddonSource {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return &GitAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GiteeAddonSource) SafeCopy() *GiteeAddonSource {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return &GiteeAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Username, Password
|
||||
func (h *HelmSource) SafeCopy() *HelmSource {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
return &HelmSource{
|
||||
URL: h.URL,
|
||||
}
|
||||
}
|
||||
|
||||
// Item is a partial interface for github.RepositoryContent
|
||||
type Item interface {
|
||||
// GetType return "dir" or "file"
|
||||
|
||||
@@ -115,3 +115,30 @@ func TestConvert2OssItem(t *testing.T) {
|
||||
assert.Equal(t, expectItemCase, addonMetas)
|
||||
|
||||
}
|
||||
|
||||
func TestSafeCopy(t *testing.T) {
|
||||
var git *GitAddonSource
|
||||
sgit := git.SafeCopy()
|
||||
assert.Nil(t, sgit)
|
||||
git = &GitAddonSource{URL: "http://github.com/kubevela", Path: "addons", Token: "123456"}
|
||||
sgit = git.SafeCopy()
|
||||
assert.Empty(t, sgit.Token)
|
||||
assert.Equal(t, "http://github.com/kubevela", sgit.URL)
|
||||
assert.Equal(t, "addons", sgit.Path)
|
||||
|
||||
var gitee *GiteeAddonSource
|
||||
sgitee := gitee.SafeCopy()
|
||||
assert.Nil(t, sgitee)
|
||||
gitee = &GiteeAddonSource{URL: "http://gitee.com/kubevela", Path: "addons", Token: "123456"}
|
||||
sgitee = gitee.SafeCopy()
|
||||
assert.Empty(t, sgitee.Token)
|
||||
assert.Equal(t, "http://gitee.com/kubevela", sgitee.URL)
|
||||
assert.Equal(t, "addons", sgitee.Path)
|
||||
|
||||
var helm *HelmSource
|
||||
shelm := helm.SafeCopy()
|
||||
assert.Nil(t, shelm)
|
||||
helm = &HelmSource{URL: "https://hub.vela.com/chartrepo/addons"}
|
||||
shelm = helm.SafeCopy()
|
||||
assert.Equal(t, "https://hub.vela.com/chartrepo/addons", shelm.URL)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ type versionedRegistry struct {
|
||||
}
|
||||
|
||||
func (i *versionedRegistry) ListAddon() ([]*UIData, error) {
|
||||
chartIndex, err := i.h.GetIndexInfo(i.url, false)
|
||||
chartIndex, err := i.h.GetIndexInfo(i.url, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *re
|
||||
}
|
||||
|
||||
func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) (*WholeAddonPackage, error) {
|
||||
versions, err := i.h.ListVersions(i.url, name, false)
|
||||
versions, err := i.h.ListVersions(i.url, name, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
|
||||
return nil, fmt.Errorf("specified version %s not exist", version)
|
||||
}
|
||||
for _, chartURL := range addonVersion.URLs {
|
||||
archive, err := common.HTTPGet(ctx, chartURL)
|
||||
archive, err := common.HTTPGetWithOption(ctx, chartURL, nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
100
pkg/apiserver/collect/suit_test.go
Normal file
100
pkg/apiserver/collect/suit_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2021 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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/kubeapi"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestCalculateJob(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Caclculate systemInfo cronJob")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
CRDDirectoryPaths: []string{"../../../charts/vela-core/crds"},
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
clients.SetKubeClient(k8sClient)
|
||||
Expect(err).Should(BeNil())
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
|
||||
switch cfg.Type {
|
||||
case "mongodb":
|
||||
ds, err = mongodb.New(context.Background(), cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
|
||||
}
|
||||
case "kubeapi":
|
||||
ds, err = kubeapi.New(context.Background(), cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("not support datastore type %s", cfg.Type)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
331
pkg/apiserver/collect/system_info_collect.go
Normal file
331
pkg/apiserver/collect/system_info_collect.go
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
Copyright 2021 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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
client2 "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
// TopKFrequent top frequency component or trait definition
|
||||
var TopKFrequent = 5
|
||||
|
||||
// CrontabSpec the cron spec of job running
|
||||
var CrontabSpec = "0 0 * * *"
|
||||
|
||||
// maximum tires is 5, initial duration is 1 minute
|
||||
var waitBackOff = wait.Backoff{
|
||||
Steps: 5,
|
||||
Duration: 1 * time.Minute,
|
||||
Factor: 5.0,
|
||||
Jitter: 0.1,
|
||||
}
|
||||
|
||||
// InfoCalculateCronJob is the cronJob to calculate the system info store in db
|
||||
type InfoCalculateCronJob struct {
|
||||
ds datastore.DataStore
|
||||
}
|
||||
|
||||
// StartCalculatingInfoCronJob will start the system info calculating job.
|
||||
func StartCalculatingInfoCronJob(ds datastore.DataStore) {
|
||||
i := InfoCalculateCronJob{
|
||||
ds: ds,
|
||||
}
|
||||
|
||||
// run calculate job in 0:00 of every day
|
||||
i.start(CrontabSpec)
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) start(cronSpec string) {
|
||||
c := cron.New(cron.WithChain(
|
||||
// don't let job panic crash whole api-server process
|
||||
cron.Recover(cron.DefaultLogger),
|
||||
))
|
||||
|
||||
// ignore the entityId and error, the cron spec is defined by hard code, mustn't generate error
|
||||
_, _ = c.AddFunc(cronSpec, func() {
|
||||
|
||||
// ExponentialBackoff retry this job
|
||||
err := retry.OnError(waitBackOff, func(err error) bool {
|
||||
// always retry
|
||||
return true
|
||||
}, func() error {
|
||||
if err := i.run(); err != nil {
|
||||
log.Logger.Errorf("Failed to calculate systemInfo, will try again after several minute error %v", err)
|
||||
return err
|
||||
}
|
||||
log.Logger.Info("Successfully to calculate systemInfo")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("After 5 tries the calculating cronJob failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
c.Start()
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) run() error {
|
||||
ctx := context.Background()
|
||||
systemInfo := model.SystemInfo{}
|
||||
e, err := i.ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if no systemInfo means velaux have not have not send get request,so skip calculate job
|
||||
if len(e) == 0 {
|
||||
return nil
|
||||
}
|
||||
info, ok := e[0].(*model.SystemInfo)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// if disable collection skip calculate job
|
||||
if !info.EnableCollection {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := i.calculateAndUpdate(ctx, *info); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAndUpdate(ctx context.Context, systemInfo model.SystemInfo) error {
|
||||
|
||||
appCount, topKComp, topKTrait, topWorkflowStep, topKPolicy, err := i.calculateAppInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enabledAddon, err := i.calculateAddonInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterCount, err := i.calculateClusterInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statisticInfo := model.StatisticInfo{
|
||||
AppCount: genCountInfo(appCount),
|
||||
TopKCompDef: topKComp,
|
||||
TopKTraitDef: topKTrait,
|
||||
TopKWorkflowStepDef: topWorkflowStep,
|
||||
TopKPolicyDef: topKPolicy,
|
||||
ClusterCount: genClusterCountInfo(clusterCount),
|
||||
EnabledAddon: enabledAddon,
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
|
||||
systemInfo.StatisticInfo = statisticInfo
|
||||
if err := i.ds.Put(ctx, &systemInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAppInfo(ctx context.Context) (int, []string, []string, []string, []string, error) {
|
||||
var err error
|
||||
var appCount int
|
||||
compDef := map[string]int{}
|
||||
traitDef := map[string]int{}
|
||||
workflowDef := map[string]int{}
|
||||
policyDef := map[string]int{}
|
||||
|
||||
var app = model.Application{}
|
||||
entities, err := i.ds.List(ctx, &app, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, entity := range entities {
|
||||
appModel, ok := entity.(*model.Application)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
appCount++
|
||||
comp := model.ApplicationComponent{
|
||||
AppPrimaryKey: appModel.Name,
|
||||
}
|
||||
comps, err := i.ds.List(ctx, &comp, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range comps {
|
||||
c, ok := e.(*model.ApplicationComponent)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
compDef[c.Type]++
|
||||
for _, t := range c.Traits {
|
||||
traitDef[t.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
workflow := model.Workflow{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
workflows, err := i.ds.List(ctx, &workflow, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range workflows {
|
||||
w, ok := e.(*model.Workflow)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, step := range w.Steps {
|
||||
workflowDef[step.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
policy := model.ApplicationPolicy{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
policies, err := i.ds.List(ctx, &policy, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range policies {
|
||||
p, ok := e.(*model.ApplicationPolicy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
policyDef[p.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
return appCount, topKFrequent(compDef, TopKFrequent), topKFrequent(traitDef, TopKFrequent), topKFrequent(workflowDef, TopKFrequent), topKFrequent(policyDef, TopKFrequent), nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAddonInfo(ctx context.Context) (map[string]string, error) {
|
||||
client, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps := &v1beta1.ApplicationList{}
|
||||
if err := client.List(ctx, apps, client2.InNamespace(types.DefaultKubeVelaNS), client2.HasLabels{oam.LabelAddonName}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := map[string]string{}
|
||||
for _, application := range apps.Items {
|
||||
if addonName := application.Labels[oam.LabelAddonName]; addonName != "" {
|
||||
var status string
|
||||
switch application.Status.Phase {
|
||||
case common.ApplicationRunning:
|
||||
status = "enabled"
|
||||
case common.ApplicationDeleting:
|
||||
status = "disabling"
|
||||
default:
|
||||
status = "enabling"
|
||||
}
|
||||
res[addonName] = status
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateClusterInfo(ctx context.Context) (int, error) {
|
||||
client, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cs, err := multicluster.ListVirtualClusters(ctx, client)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(cs), nil
|
||||
}
|
||||
|
||||
type defPair struct {
|
||||
name string
|
||||
count int
|
||||
}
|
||||
|
||||
func topKFrequent(defs map[string]int, k int) []string {
|
||||
var pairs []defPair
|
||||
var res []string
|
||||
for name, num := range defs {
|
||||
pairs = append(pairs, defPair{name: name, count: num})
|
||||
}
|
||||
sort.Slice(pairs, func(i, j int) bool {
|
||||
return pairs[i].count >= pairs[j].count
|
||||
})
|
||||
i := 0
|
||||
for _, pair := range pairs {
|
||||
res = append(res, pair.name)
|
||||
i++
|
||||
if i == k {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func genCountInfo(num int) string {
|
||||
switch {
|
||||
case num < 10:
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
case num < 100:
|
||||
return "<100"
|
||||
case num < 500:
|
||||
return "<500"
|
||||
case num < 2000:
|
||||
return "<2000"
|
||||
case num < 5000:
|
||||
return "<5000"
|
||||
case num < 10000:
|
||||
return "<10000"
|
||||
default:
|
||||
return ">=10000"
|
||||
}
|
||||
}
|
||||
|
||||
func genClusterCountInfo(num int) string {
|
||||
switch {
|
||||
case num < 3:
|
||||
return "<3"
|
||||
case num < 10:
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
default:
|
||||
return ">=50"
|
||||
}
|
||||
}
|
||||
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
Copyright 2021 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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"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/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test calculate cronJob", func() {
|
||||
var (
|
||||
ds datastore.DataStore
|
||||
testProject string
|
||||
i InfoCalculateCronJob
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
mockDataInDs := func() {
|
||||
app1 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app1", Project: testProject}
|
||||
app2 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app2", Project: testProject}
|
||||
trait1 := model.ApplicationTrait{Type: "rollout"}
|
||||
trait2 := model.ApplicationTrait{Type: "expose"}
|
||||
trait3 := model.ApplicationTrait{Type: "rollout"}
|
||||
trait4 := model.ApplicationTrait{Type: "patch"}
|
||||
trait5 := model.ApplicationTrait{Type: "patch"}
|
||||
trait6 := model.ApplicationTrait{Type: "rollout"}
|
||||
appComp1 := model.ApplicationComponent{AppPrimaryKey: app1.PrimaryKey(), Name: "comp1", Type: "helm", Traits: []model.ApplicationTrait{trait1, trait4}}
|
||||
appComp2 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp2", Type: "webservice", Traits: []model.ApplicationTrait{trait3}}
|
||||
appComp3 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp3", Type: "webservice", Traits: []model.ApplicationTrait{trait2, trait5, trait6}}
|
||||
Expect(ds.Add(ctx, &app1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &app2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp3)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-fluxcd", Labels: map[string]string{oam.LabelAddonName: "fluxcd"}}, Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-rollout", Labels: map[string]string{oam.LabelAddonName: "rollout"}}, Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
|
||||
Expect(ds).ShouldNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
testProject = "test-cronjob-project"
|
||||
mockDataInDs()
|
||||
i = InfoCalculateCronJob{
|
||||
ds: ds,
|
||||
}
|
||||
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: true}
|
||||
Expect(ds.Add(ctx, &systemInfo)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
})
|
||||
|
||||
It("Test calculate app Info", func() {
|
||||
appNum, topKCom, topKTrait, _, _, err := i.calculateAppInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(appNum).Should(BeEquivalentTo(2))
|
||||
Expect(topKCom).Should(BeEquivalentTo([]string{"webservice", "helm"}))
|
||||
Expect(topKTrait).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
|
||||
})
|
||||
|
||||
It("Test calculate addon Info", func() {
|
||||
enabledAddon, err := i.calculateAddonInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(enabledAddon).Should(BeEquivalentTo(map[string]string{
|
||||
"fluxcd": "enabling",
|
||||
"rollout": "enabling",
|
||||
}))
|
||||
})
|
||||
|
||||
It("Test calculate cluster Info", func() {
|
||||
clusterNum, err := i.calculateClusterInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(clusterNum).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("Test calculateAndUpdate func", func() {
|
||||
systemInfo := model.SystemInfo{}
|
||||
es, err := ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(es)).Should(BeEquivalentTo(1))
|
||||
info, ok := es[0].(*model.SystemInfo)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
|
||||
|
||||
Expect(i.calculateAndUpdate(ctx, *info)).Should(BeNil())
|
||||
|
||||
systemInfo = model.SystemInfo{}
|
||||
es, err = ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(es)).Should(BeEquivalentTo(1))
|
||||
info, ok = es[0].(*model.SystemInfo)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
|
||||
Expect(info.StatisticInfo.AppCount).Should(BeEquivalentTo("<10"))
|
||||
Expect(info.StatisticInfo.ClusterCount).Should(BeEquivalentTo("<3"))
|
||||
Expect(info.StatisticInfo.TopKCompDef).Should(BeEquivalentTo([]string{"webservice", "helm"}))
|
||||
Expect(info.StatisticInfo.TopKTraitDef).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
|
||||
Expect(info.StatisticInfo.EnabledAddon).Should(BeEquivalentTo(map[string]string{
|
||||
"fluxcd": "enabling",
|
||||
"rollout": "enabling",
|
||||
}))
|
||||
})
|
||||
|
||||
It("Test run func", func() {
|
||||
app3 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app3", Project: testProject}
|
||||
Expect(ds.Add(ctx, &app3)).Should(BeNil())
|
||||
|
||||
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: false}
|
||||
Expect(ds.Put(ctx, &systemInfo)).Should(BeNil())
|
||||
Expect(i.run()).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
func TestGenCountInfo(t *testing.T) {
|
||||
testcases := []struct {
|
||||
count int
|
||||
res string
|
||||
}{
|
||||
{
|
||||
count: 3,
|
||||
res: "<10",
|
||||
},
|
||||
{
|
||||
count: 14,
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 80,
|
||||
res: "<100",
|
||||
},
|
||||
{
|
||||
count: 350,
|
||||
res: "<500",
|
||||
},
|
||||
{
|
||||
count: 1800,
|
||||
res: "<2000",
|
||||
},
|
||||
{
|
||||
count: 4000,
|
||||
res: "<5000",
|
||||
},
|
||||
{
|
||||
count: 9000,
|
||||
res: "<10000",
|
||||
},
|
||||
{
|
||||
count: 30000,
|
||||
res: ">=10000",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, genCountInfo(testcase.count), testcase.res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenClusterCountInfo(t *testing.T) {
|
||||
testcases := []struct {
|
||||
count int
|
||||
res string
|
||||
}{
|
||||
{
|
||||
count: 2,
|
||||
res: "<3",
|
||||
},
|
||||
{
|
||||
count: 7,
|
||||
res: "<10",
|
||||
},
|
||||
{
|
||||
count: 34,
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
res: ">=50",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, genClusterCountInfo(testcase.count), testcase.res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopKFrequent(t *testing.T) {
|
||||
testCases := []struct {
|
||||
def map[string]int
|
||||
k int
|
||||
res []string
|
||||
}{
|
||||
{
|
||||
def: map[string]int{
|
||||
"rollout": 4,
|
||||
"patch": 3,
|
||||
"expose": 6,
|
||||
},
|
||||
k: 3,
|
||||
res: []string{"expose", "rollout", "patch"},
|
||||
},
|
||||
{
|
||||
// just return top2
|
||||
def: map[string]int{
|
||||
"rollout": 4,
|
||||
"patch": 3,
|
||||
"expose": 6,
|
||||
},
|
||||
k: 2,
|
||||
res: []string{"expose", "rollout"},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
assert.DeepEqual(t, topKFrequent(testCase.def, testCase.k), testCase.res)
|
||||
}
|
||||
}
|
||||
|
||||
type DataExistMatcher struct{}
|
||||
|
||||
// Match matches error.
|
||||
func (matcher DataExistMatcher) Match(actual interface{}) (success bool, err error) {
|
||||
if actual == nil {
|
||||
return false, nil
|
||||
}
|
||||
actualError := actual.(error)
|
||||
return errors.Is(actualError, datastore.ErrRecordExist), nil
|
||||
}
|
||||
|
||||
// FailureMessage builds an error message.
|
||||
func (matcher DataExistMatcher) FailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "to be already exist")
|
||||
}
|
||||
|
||||
// NegatedFailureMessage builds an error message.
|
||||
func (matcher DataExistMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "not to be already exist")
|
||||
}
|
||||
@@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
func init() {
|
||||
RegisterModel(&SystemInfo{})
|
||||
}
|
||||
@@ -30,9 +32,51 @@ const (
|
||||
// SystemInfo systemInfo model
|
||||
type SystemInfo struct {
|
||||
BaseModel
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
DexConfig DexConfig `json:"dexConfig,omitempty"`
|
||||
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
|
||||
}
|
||||
|
||||
// DexConfig dex config
|
||||
type DexConfig struct {
|
||||
Issuer string `json:"issuer"`
|
||||
Web DexWeb `json:"web"`
|
||||
Storage DexStorage `json:"storage"`
|
||||
StaticClients []DexStaticClient `json:"staticClients"`
|
||||
Connectors []interface{} `json:"connectors,omitempty"`
|
||||
EnablePasswordDB bool `json:"enablePasswordDB"`
|
||||
}
|
||||
|
||||
// StatisticInfo the system statistic info
|
||||
type StatisticInfo struct {
|
||||
ClusterCount string `json:"clusterCount,omitempty"`
|
||||
AppCount string `json:"appCount,omitempty"`
|
||||
EnabledAddon map[string]string `json:"enabledAddon,omitempty"`
|
||||
TopKCompDef []string `json:"topKCompDef,omitempty"`
|
||||
TopKTraitDef []string `json:"topKTraitDef,omitempty"`
|
||||
TopKWorkflowStepDef []string `json:"topKWorkflowStepDef,omitempty"`
|
||||
TopKPolicyDef []string `json:"topKPolicyDef,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// DexStorage dex storage
|
||||
type DexStorage struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// DexWeb dex web
|
||||
type DexWeb struct {
|
||||
HTTP string `json:"http"`
|
||||
}
|
||||
|
||||
// DexStaticClient dex static client
|
||||
type DexStaticClient struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Secret string `json:"secret"`
|
||||
RedirectURIs []string `json:"redirectURIs"`
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
|
||||
@@ -184,6 +184,29 @@ type AddonArgsResponse struct {
|
||||
Args map[string]string `json:"args"`
|
||||
}
|
||||
|
||||
// ConfigType define the format for listing configuration types
|
||||
type ConfigType struct {
|
||||
Definitions []string `json:"definitions"`
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// Config define the metadata of a config
|
||||
type Config struct {
|
||||
ConfigType string `json:"configType"`
|
||||
ConfigTypeAlias string `json:"configTypeAlias"`
|
||||
Name string `json:"name"`
|
||||
Project string `json:"project"`
|
||||
Identifier string `json:"identifier"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
CreatedTime *time.Time `json:"createdTime"`
|
||||
UpdatedTime *time.Time `json:"updatedTime"`
|
||||
ApplicationStatus common.ApplicationPhase `json:"applicationStatus"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// AccessKeyRequest request parameters to access cloud provider
|
||||
type AccessKeyRequest struct {
|
||||
AccessKeyID string `json:"accessKeyID"`
|
||||
@@ -382,6 +405,16 @@ type CreateApplicationRequest struct {
|
||||
Component *CreateComponentRequest `json:"component"`
|
||||
}
|
||||
|
||||
// CreateConfigRequest is the request body to creates a config
|
||||
type CreateConfigRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Project string `json:"project"`
|
||||
ComponentType string `json:"componentType" validate:"checkname"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateApplicationRequest update application base config
|
||||
type UpdateApplicationRequest struct {
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
@@ -1081,14 +1114,36 @@ type DetailRevisionResponse struct {
|
||||
|
||||
// SystemInfoResponse get SystemInfo
|
||||
type SystemInfoResponse struct {
|
||||
model.SystemInfo
|
||||
SystemInfo
|
||||
SystemVersion SystemVersion `json:"systemVersion"`
|
||||
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfo system info
|
||||
type SystemInfo struct {
|
||||
PlatformID string `json:"platformID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
InstallTime time.Time `json:"installTime,omitempty"`
|
||||
}
|
||||
|
||||
// StatisticInfo generated by cronJob running in backend
|
||||
type StatisticInfo struct {
|
||||
ClusterCount string `json:"clusterCount,omitempty"`
|
||||
AppCount string `json:"appCount,omitempty"`
|
||||
EnableAddonList map[string]string `json:"enableAddonList,omitempty"`
|
||||
ComponentDefinitionTopList []string `json:"componentDefinitionTopList,omitempty"`
|
||||
TraitDefinitionTopList []string `json:"traitDefinitionTopList,omitempty"`
|
||||
WorkflowDefinitionTopList []string `json:"workflowDefinitionTopList,omitempty"`
|
||||
PolicyDefinitionTopList []string `json:"policyDefinitionTopList,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfoRequest request by update SystemInfo
|
||||
type SystemInfoRequest struct {
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
VelaAddress string `json:"velaAddress,omitempty"`
|
||||
}
|
||||
|
||||
// SystemVersion contains KubeVela version
|
||||
@@ -1277,3 +1332,14 @@ type LoginUserInfoResponse struct {
|
||||
PlatformPermissions []PermissionBase `json:"platformPermissions"`
|
||||
ProjectPermissions map[string][]PermissionBase `json:"projectPermissions"`
|
||||
}
|
||||
|
||||
// ChartRepoResponse the response body of chart repo
|
||||
type ChartRepoResponse struct {
|
||||
URL string `json:"url"`
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
// ChartRepoResponseList the response body of list chart repo
|
||||
type ChartRepoResponseList struct {
|
||||
ChartRepoResponse []*ChartRepoResponse `json:"repos"`
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/collect"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/go-openapi/spec"
|
||||
@@ -60,6 +62,9 @@ type Config struct {
|
||||
|
||||
// AddonCacheTime is how long between two cache operations
|
||||
AddonCacheTime time.Duration
|
||||
|
||||
// DisableStatisticCronJob close the calculate system info cronJob
|
||||
DisableStatisticCronJob bool
|
||||
}
|
||||
|
||||
type leaderConfig struct {
|
||||
@@ -141,6 +146,10 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
|
||||
Callbacks: leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: func(ctx context.Context) {
|
||||
go velasync.Start(ctx, s.dataStore, restCfg, s.usecases)
|
||||
if !s.cfg.DisableStatisticCronJob {
|
||||
collect.StartCalculatingInfoCronJob(s.dataStore)
|
||||
}
|
||||
// this process would block the whole process, any other handler should start before this func
|
||||
s.runWorkflowRecordSync(ctx, s.cfg.LeaderConfig.Duration)
|
||||
},
|
||||
OnStoppedLeading: func() {
|
||||
|
||||
@@ -314,10 +314,10 @@ func (u *defaultAddonHandler) CreateAddonRegistry(ctx context.Context, req apis.
|
||||
func convertAddonRegistry(r pkgaddon.Registry) *apis.AddonRegistry {
|
||||
return &apis.AddonRegistry{
|
||||
Name: r.Name,
|
||||
Git: r.Git,
|
||||
Gitee: r.Gitee,
|
||||
Git: r.Git.SafeCopy(),
|
||||
Gitee: r.Gitee.SafeCopy(),
|
||||
OSS: r.OSS,
|
||||
Helm: r.Helm,
|
||||
Helm: r.Helm.SafeCopy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,8 @@ func (u *defaultAddonHandler) EnableAddon(ctx context.Context, name string, args
|
||||
}
|
||||
|
||||
// wrap this error with special bcode
|
||||
if errors.Is(err, pkgaddon.ErrVersionMismatch) {
|
||||
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
|
||||
log.Logger.Error(err)
|
||||
return bcode.ErrAddonSystemVersionMismatch
|
||||
}
|
||||
// except `addon not found`, other errors should return directly
|
||||
@@ -464,7 +465,7 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args
|
||||
}
|
||||
|
||||
// wrap this error with special bcode
|
||||
if errors.Is(err, pkgaddon.ErrVersionMismatch) {
|
||||
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
|
||||
return bcode.ErrAddonSystemVersionMismatch
|
||||
}
|
||||
// except `addon not found`, other errors should return directly
|
||||
|
||||
@@ -33,18 +33,18 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
@@ -686,7 +686,7 @@ func (c *applicationUsecaseImpl) DetailPolicy(ctx context.Context, app *model.Ap
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deploy deploy app to cluster
|
||||
// Deploy deploys app to cluster
|
||||
// means to render oam application config and apply to cluster.
|
||||
// An event record is generated for each deploy.
|
||||
func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error) {
|
||||
@@ -704,6 +704,11 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sync configs to clusters
|
||||
if err := c.syncConfigs4Application(ctx, oamApp, app.Project, workflow.EnvName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// step2: check and create deploy event
|
||||
if !req.Force {
|
||||
var lastVersion = model.ApplicationRevision{
|
||||
@@ -792,6 +797,44 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sync configs to clusters
|
||||
func (c *applicationUsecaseImpl) syncConfigs4Application(ctx context.Context, app *v1beta1.Application, projectName, envName string) error {
|
||||
var areTerraformComponents = true
|
||||
for _, m := range app.Spec.Components {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
if err := c.kubeClient.Get(ctx, client.ObjectKey{Namespace: velatypes.DefaultKubeVelaNS, Name: m.Type}, d); err != nil {
|
||||
klog.ErrorS(err, "failed to get config type", "ComponentDefinition", m.Type)
|
||||
}
|
||||
// check the type of the componentDefinition is Terraform
|
||||
if d.Spec.Schematic != nil && d.Spec.Schematic.Terraform == nil {
|
||||
areTerraformComponents = false
|
||||
}
|
||||
}
|
||||
// skip configs sync
|
||||
if areTerraformComponents {
|
||||
return nil
|
||||
}
|
||||
env, err := c.envUsecase.GetEnv(ctx, envName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var clusterTargets []*model.ClusterTarget
|
||||
for _, t := range env.Targets {
|
||||
target, err := c.targetUsecase.GetTarget(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Cluster != nil {
|
||||
clusterTargets = append(clusterTargets, target.Cluster)
|
||||
}
|
||||
}
|
||||
|
||||
if err := SyncConfigs(ctx, c.kubeClient, projectName, clusterTargets); err != nil {
|
||||
return fmt.Errorf("sync config failure %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) {
|
||||
// Priority 1 uses the requested workflow as release .
|
||||
// Priority 2 uses the default workflow as release .
|
||||
|
||||
@@ -18,6 +18,7 @@ package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -25,26 +26,30 @@ import (
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/form3tech-oss/jwt-go"
|
||||
"golang.org/x/oauth2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
const (
|
||||
secretDexConfig = "dex-config"
|
||||
keyDex = "dex"
|
||||
dexConfigName = "dex-config"
|
||||
secretDexConfigKey = "config.yaml"
|
||||
dexAddonName = "addon-dex"
|
||||
jwtIssuer = "vela-issuer"
|
||||
signedKey = "vela-singned"
|
||||
|
||||
// GrantTypeAccess is the grant type for access token
|
||||
GrantTypeAccess = "access"
|
||||
@@ -52,12 +57,15 @@ const (
|
||||
GrantTypeRefresh = "refresh"
|
||||
)
|
||||
|
||||
var signedKey = ""
|
||||
|
||||
// AuthenticationUsecase is the usecase of authentication
|
||||
type AuthenticationUsecase interface {
|
||||
Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error)
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error)
|
||||
GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error)
|
||||
GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error)
|
||||
UpdateDexConfig(ctx context.Context) error
|
||||
}
|
||||
|
||||
type authenticationUsecaseImpl struct {
|
||||
@@ -86,7 +94,6 @@ type authHandler interface {
|
||||
}
|
||||
|
||||
type dexHandlerImpl struct {
|
||||
token *oauth2.Token
|
||||
idToken *oidc.IDToken
|
||||
ds datastore.DataStore
|
||||
}
|
||||
@@ -127,7 +134,6 @@ func (a *authenticationUsecaseImpl) newDexHandler(ctx context.Context, req apisv
|
||||
return nil, err
|
||||
}
|
||||
return &dexHandlerImpl{
|
||||
token: token,
|
||||
idToken: idToken,
|
||||
ds: a.ds,
|
||||
}, nil
|
||||
@@ -148,7 +154,7 @@ func (a *authenticationUsecaseImpl) newLocalHandler(req apisv1.LoginRequest) (*l
|
||||
func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error) {
|
||||
var handler authHandler
|
||||
var err error
|
||||
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
|
||||
sysInfo, err := a.sysUsecase.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,13 +208,15 @@ func (a *authenticationUsecaseImpl) generateJWTToken(username, grantType string,
|
||||
GrantType: grantType,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
return token.SignedString([]byte(signedKey))
|
||||
}
|
||||
|
||||
func (a *authenticationUsecaseImpl) RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error) {
|
||||
claim, err := ParseToken(refreshToken)
|
||||
if err != nil {
|
||||
if errors.Is(err, bcode.ErrTokenExpired) {
|
||||
return nil, bcode.ErrRefreshTokenExpired
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if claim.GrantType == GrantTypeRefresh {
|
||||
@@ -253,33 +261,10 @@ func ParseToken(tokenString string) (*model.CustomClaims, error) {
|
||||
}
|
||||
|
||||
func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error) {
|
||||
secret := &v1.Secret{}
|
||||
if err := a.kubeClient.Get(ctx, types.NamespacedName{
|
||||
Name: secretDexConfig,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
}, secret); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, bcode.ErrDexConfigNotFound
|
||||
}
|
||||
log.Logger.Errorf("failed to get dex config: %s", err.Error())
|
||||
config, err := getDexConfig(ctx, a.kubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var config struct {
|
||||
Issuer string `json:"issuer"`
|
||||
StaticClients []struct {
|
||||
ID string `json:"id"`
|
||||
Secret string `json:"secret"`
|
||||
RedirectURIs []string `json:"redirectURIs"`
|
||||
} `json:"staticClients"`
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(secret.Data[secretDexConfigKey], &config); err != nil {
|
||||
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
|
||||
return nil, bcode.ErrInvalidDexConfig
|
||||
}
|
||||
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
|
||||
return nil, bcode.ErrInvalidDexConfig
|
||||
}
|
||||
return &apisv1.DexConfigResponse{
|
||||
Issuer: config.Issuer,
|
||||
ClientID: config.StaticClients[0].ID,
|
||||
@@ -288,8 +273,121 @@ func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.D
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *authenticationUsecaseImpl) UpdateDexConfig(ctx context.Context) error {
|
||||
connectors, err := utils.GetDexConnectors(ctx, a.kubeClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dexConfig := &corev1.Secret{}
|
||||
if err := a.kubeClient.Get(ctx, types.NamespacedName{
|
||||
Name: dexConfigName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
}, dexConfig); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config := &model.JSONStruct{}
|
||||
if dexConfig == nil || dexConfig.Data == nil {
|
||||
(*config)["connectors"] = connectors
|
||||
c, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.kubeClient.Create(ctx, &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: dexConfigName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
secretDexConfigKey: c,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = yaml.Unmarshal(dexConfig.Data[secretDexConfigKey], config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*config)["connectors"] = connectors
|
||||
c, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dexConfig.Data[secretDexConfigKey] = c
|
||||
if err := a.kubeClient.Update(ctx, dexConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return restartDex(ctx, a.kubeClient)
|
||||
}
|
||||
|
||||
func restartDex(ctx context.Context, kubeClient client.Client) error {
|
||||
dexApp := &v1beta1.Application{}
|
||||
if err := kubeClient.Get(ctx, types.NamespacedName{
|
||||
Name: dexAddonName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
}, dexApp); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return bcode.ErrDexNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
for i, comp := range dexApp.Spec.Components {
|
||||
if comp.Name == keyDex {
|
||||
var v model.JSONStruct
|
||||
err := json.Unmarshal(comp.Properties.Raw, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// restart the dex server
|
||||
if _, ok := v["values"]; ok {
|
||||
v["values"].(map[string]interface{})["env"] = map[string]string{
|
||||
"TIME_STAMP": time.Now().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
dexApp.Spec.Components[i].Properties = v.RawExtension()
|
||||
if err := kubeClient.Update(ctx, dexApp); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDexConfig(ctx context.Context, kubeClient client.Client) (*model.DexConfig, error) {
|
||||
dexConfigSecret := &corev1.Secret{}
|
||||
if err := kubeClient.Get(ctx, types.NamespacedName{
|
||||
Name: dexConfigName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
}, dexConfigSecret); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil, bcode.ErrDexConfigNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if dexConfigSecret.Data == nil {
|
||||
return nil, bcode.ErrInvalidDexConfig
|
||||
}
|
||||
|
||||
config := &model.DexConfig{}
|
||||
if err := yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config); err != nil {
|
||||
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
|
||||
return nil, bcode.ErrInvalidDexConfig
|
||||
}
|
||||
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
|
||||
return nil, bcode.ErrInvalidDexConfig
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (a *authenticationUsecaseImpl) GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error) {
|
||||
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
|
||||
sysInfo, err := a.sysUsecase.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,19 +410,18 @@ func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {
|
||||
}
|
||||
|
||||
user := &model.User{Email: claims.Email}
|
||||
userBase := &apisv1.UserBase{Email: claims.Email, Name: claims.Name}
|
||||
users, err := d.ds.List(ctx, user, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(users) > 0 {
|
||||
u := users[0].(*model.User)
|
||||
if u.Name != claims.Name {
|
||||
u.Name = claims.Name
|
||||
}
|
||||
u.LastLoginTime = time.Now()
|
||||
if err := d.ds.Put(ctx, u); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userBase.Name = u.Name
|
||||
} else if err := d.ds.Add(ctx, &model.User{
|
||||
Email: claims.Email,
|
||||
Name: claims.Name,
|
||||
@@ -333,10 +430,7 @@ func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apisv1.UserBase{
|
||||
Name: claims.Name,
|
||||
Email: claims.Email,
|
||||
}, nil
|
||||
return userBase, nil
|
||||
}
|
||||
|
||||
func (l *localHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package usecase
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -27,13 +28,18 @@ import (
|
||||
"github.com/coreos/go-oidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/oauth2"
|
||||
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/yaml"
|
||||
|
||||
"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/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
@@ -51,7 +57,7 @@ var _ = Describe("Test authentication usecase functions", func() {
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
authUsecase = &authenticationUsecaseImpl{kubeClient: k8sClient, ds: ds}
|
||||
sysUsecase = &systemInfoUsecaseImpl{ds: ds}
|
||||
sysUsecase = &systemInfoUsecaseImpl{ds: ds, kubeClient: k8sClient}
|
||||
userUsecase = &userUsecaseImpl{ds: ds, sysUsecase: sysUsecase}
|
||||
})
|
||||
It("Test Dex login", func() {
|
||||
@@ -61,10 +67,6 @@ var _ = Describe("Test authentication usecase functions", func() {
|
||||
})
|
||||
defer patch.Reset()
|
||||
dexHandler := dexHandlerImpl{
|
||||
token: &oauth2.Token{
|
||||
AccessToken: "access-token",
|
||||
RefreshToken: "refresh-token",
|
||||
},
|
||||
idToken: testIDToken,
|
||||
ds: ds,
|
||||
}
|
||||
@@ -79,6 +81,20 @@ var _ = Describe("Test authentication usecase functions", func() {
|
||||
err = ds.Get(context.Background(), user)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(user.Email).Should(Equal("test@test.com"))
|
||||
|
||||
existUser := &model.User{
|
||||
Name: "test",
|
||||
}
|
||||
err = ds.Delete(context.Background(), existUser)
|
||||
Expect(err).Should(BeNil())
|
||||
existUser.Name = "exist-user"
|
||||
existUser.Email = "test@test.com"
|
||||
err = ds.Add(context.Background(), existUser)
|
||||
Expect(err).Should(BeNil())
|
||||
resp, err = dexHandler.login(context.Background())
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(resp.Email).Should(Equal("test@test.com"))
|
||||
Expect(resp.Name).Should(Equal("exist-user"))
|
||||
})
|
||||
|
||||
It("Test local login", func() {
|
||||
@@ -99,37 +115,87 @@ var _ = Describe("Test authentication usecase functions", func() {
|
||||
Expect(resp.Name).Should(Equal("test-login"))
|
||||
})
|
||||
|
||||
It("Test get dex config", func() {
|
||||
It("Test update dex config", func() {
|
||||
err := k8sClient.Create(context.Background(), &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-system",
|
||||
},
|
||||
})
|
||||
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
err = k8sClient.Create(context.Background(), &corev1.Secret{
|
||||
webserver, err := ioutil.ReadFile("./testdata/dex-config-def.yaml")
|
||||
Expect(err).Should(Succeed())
|
||||
var cd v1beta1.ComponentDefinition
|
||||
err = yaml.Unmarshal(webserver, &cd)
|
||||
Expect(err).Should(Succeed())
|
||||
err = k8sClient.Create(context.Background(), &cd)
|
||||
Expect(err).Should(Succeed())
|
||||
err = k8sClient.Create(context.Background(), &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretDexConfig,
|
||||
Name: "addon-dex",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
secretDexConfigKey: `
|
||||
issuer: https://dex.oam.dev
|
||||
staticClients:
|
||||
- id: client-id
|
||||
secret: client-secret
|
||||
redirectURIs:
|
||||
- http://localhost:8080/auth/callback
|
||||
`,
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "dex",
|
||||
// only for test here
|
||||
Type: "dex-config",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"values":{"test":"test"}}`)},
|
||||
Traits: []common.ApplicationTrait{},
|
||||
Scopes: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
config, err := authUsecase.GetDexConfig(context.Background())
|
||||
err = k8sClient.Create(context.Background(), &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a",
|
||||
Namespace: "vela-system",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/source-of-truth": "from-inner-system",
|
||||
"config.oam.dev/catalog": "velacore-config",
|
||||
"config.oam.dev/type": "config-dex-connector",
|
||||
"config.oam.dev/sub-type": "ldap",
|
||||
"project": "abc",
|
||||
},
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"ldap": `{"clientID":"clientID","clientSecret":"clientSecret"}`,
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
By("try to update dex config without config secret")
|
||||
err = authUsecase.UpdateDexConfig(context.Background())
|
||||
Expect(err).Should(BeNil())
|
||||
dexConfigSecret := &corev1.Secret{}
|
||||
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "dex-config", Namespace: "vela-system"}, dexConfigSecret)
|
||||
Expect(err).Should(BeNil())
|
||||
config := &model.DexConfig{}
|
||||
err = yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(config.Connectors)).Should(Equal(1))
|
||||
By("try to update dex config with config secret")
|
||||
err = authUsecase.UpdateDexConfig(context.Background())
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(config.Issuer).Should(Equal("https://dex.oam.dev"))
|
||||
Expect(config.ClientID).Should(Equal("client-id"))
|
||||
Expect(config.ClientSecret).Should(Equal("client-secret"))
|
||||
Expect(config.RedirectURL).Should(Equal("http://localhost:8080/auth/callback"))
|
||||
})
|
||||
|
||||
It("Test get dex config", func() {
|
||||
_, err := authUsecase.GetDexConfig(context.Background())
|
||||
Expect(err).Should(Equal(bcode.ErrInvalidDexConfig))
|
||||
err = ds.Add(context.Background(), &model.User{Name: "admin", Email: "test@test.com"})
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = sysUsecase.UpdateSystemInfo(context.Background(), apisv1.SystemInfoRequest{
|
||||
LoginType: model.LoginTypeDex,
|
||||
VelaAddress: "http://velaux.com",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
config, err := authUsecase.GetDexConfig(context.Background())
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(config.Issuer).Should(Equal("http://velaux.com/dex"))
|
||||
Expect(config.ClientID).Should(Equal("velaux"))
|
||||
Expect(config.ClientSecret).Should(Equal("velaux-secret"))
|
||||
Expect(config.RedirectURL).Should(Equal("http://velaux.com/callback"))
|
||||
})
|
||||
})
|
||||
|
||||
477
pkg/apiserver/rest/usecase/config.go
Normal file
477
pkg/apiserver/rest/usecase/config.go
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
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 usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
set "github.com/deckarep/golang-set"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
)
|
||||
|
||||
const (
|
||||
definitionAlias = definition.UserPrefix + "alias.config.oam.dev"
|
||||
definitionType = definition.UserPrefix + "type.config.oam.dev"
|
||||
|
||||
configIsReady = "Ready"
|
||||
configIsNotReady = "Not ready"
|
||||
terraformProviderAlias = "Terraform Cloud Provider"
|
||||
configSyncProjectPrefix = "config-sync"
|
||||
)
|
||||
|
||||
// ConfigHandler handle CRUD of configs
|
||||
type ConfigHandler interface {
|
||||
ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error)
|
||||
GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error)
|
||||
CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error
|
||||
GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error)
|
||||
GetConfig(ctx context.Context, configType, name string) (*apis.Config, error)
|
||||
DeleteConfig(ctx context.Context, configType, name string) error
|
||||
}
|
||||
|
||||
// NewConfigUseCase returns a config use case
|
||||
func NewConfigUseCase(authenticationUseCase AuthenticationUsecase) ConfigHandler {
|
||||
k8sClient, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &configUseCaseImpl{
|
||||
authenticationUseCase: authenticationUseCase,
|
||||
kubeClient: k8sClient,
|
||||
}
|
||||
}
|
||||
|
||||
type configUseCaseImpl struct {
|
||||
kubeClient client.Client
|
||||
authenticationUseCase AuthenticationUsecase
|
||||
}
|
||||
|
||||
// ListConfigTypes returns all config types
|
||||
func (u *configUseCaseImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) {
|
||||
defs := &v1beta1.ComponentDefinitionList{}
|
||||
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tfDefs []v1beta1.ComponentDefinition
|
||||
var configTypes []*apis.ConfigType
|
||||
|
||||
for _, d := range defs.Items {
|
||||
if d.Labels[definitionType] == types.TerraformProvider {
|
||||
tfDefs = append(tfDefs, d)
|
||||
continue
|
||||
}
|
||||
configTypes = append(configTypes, &apis.ConfigType{
|
||||
Alias: d.Annotations[definitionAlias],
|
||||
Name: d.Name,
|
||||
Definitions: []string{d.Name},
|
||||
Description: d.Annotations[types.AnnoDefinitionDescription],
|
||||
})
|
||||
}
|
||||
|
||||
if len(tfDefs) > 0 {
|
||||
tfType := &apis.ConfigType{
|
||||
Alias: terraformProviderAlias,
|
||||
Name: types.TerraformProvider,
|
||||
}
|
||||
definitions := make([]string, len(tfDefs))
|
||||
|
||||
for i, tf := range tfDefs {
|
||||
definitions[i] = tf.Name
|
||||
}
|
||||
tfType.Definitions = definitions
|
||||
|
||||
return append(configTypes, tfType), nil
|
||||
}
|
||||
return configTypes, nil
|
||||
}
|
||||
|
||||
// GetConfigType returns a config type
|
||||
func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: configType}, d); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get config type")
|
||||
}
|
||||
|
||||
t := &apis.ConfigType{
|
||||
Alias: d.Annotations[definitionAlias],
|
||||
Name: configType,
|
||||
Description: d.Annotations[types.AnnoDefinitionDescription],
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
|
||||
p := req.Properties
|
||||
// If the component is Terraform type, set the provider name same as the application name and the component name
|
||||
if strings.HasPrefix(req.ComponentType, types.TerraformComponentPrefix) {
|
||||
var properties map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(p), &properties); err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
properties["name"] = req.Name
|
||||
tmp, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
p = string(tmp)
|
||||
}
|
||||
ui := config.UIParam{
|
||||
Alias: req.Alias,
|
||||
Description: req.Description,
|
||||
Project: req.Project,
|
||||
}
|
||||
return config.CreateApplication(ctx, u.kubeClient, req.Name, req.ComponentType, p, ui)
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) {
|
||||
switch configType {
|
||||
case types.TerraformProvider:
|
||||
providers, err := config.ListTerraformProviders(ctx, u.kubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configs := make([]*apis.Config, len(providers))
|
||||
for i, p := range providers {
|
||||
var a v1beta1.Application
|
||||
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: p.Name}, &a); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
t := p.CreationTimestamp.Time
|
||||
configs[i] = &apis.Config{
|
||||
Name: p.Name,
|
||||
CreatedTime: &t,
|
||||
}
|
||||
if p.Status.State == terraformtypes.ProviderIsReady {
|
||||
configs[i].Status = configIsReady
|
||||
} else {
|
||||
configs[i].Status = configIsNotReady
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// If the application doesn't have any components, skip it as something wrong happened.
|
||||
if !strings.HasPrefix(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
}
|
||||
return configs, nil
|
||||
|
||||
default:
|
||||
return u.getConfigsByConfigType(ctx, configType)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configType string) ([]*apis.Config, error) {
|
||||
var apps = &v1beta1.ApplicationList{}
|
||||
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: configType,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configs := make([]*apis.Config, len(apps.Items))
|
||||
for i, a := range apps.Items {
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) {
|
||||
var a = &v1beta1.Application{}
|
||||
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &apis.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: a.Labels[types.LabelConfigProject],
|
||||
CreatedTime: &a.CreationTimestamp.Time,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) DeleteConfig(ctx context.Context, configType, name string) error {
|
||||
var isTerraformProvider bool
|
||||
if strings.HasPrefix(configType, types.TerraformComponentPrefix) {
|
||||
isTerraformProvider = true
|
||||
}
|
||||
return config.DeleteApplication(ctx, u.kubeClient, name, isTerraformProvider)
|
||||
}
|
||||
|
||||
// ApplicationDeployTarget is the struct of application deploy target
|
||||
type ApplicationDeployTarget struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Clusters []string `json:"clusters"`
|
||||
}
|
||||
|
||||
// SyncConfigs will sync configs to working clusters
|
||||
func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, targets []*model.ClusterTarget) error {
|
||||
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
|
||||
// get all configs which can be synced to working clusters in the project
|
||||
var secrets v1.SecretList
|
||||
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(secrets.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
var objects []map[string]string
|
||||
for _, s := range secrets.Items {
|
||||
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
|
||||
objects = append(objects, map[string]string{
|
||||
"name": s.Name,
|
||||
"resource": "secret",
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(objects) == 0 {
|
||||
klog.InfoS("no configs need to sync to working clusters", "project", project)
|
||||
return nil
|
||||
}
|
||||
objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var app = &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
// config sync application doesn't exist, create one
|
||||
clusterTargets := convertClusterTargets(targets)
|
||||
if len(clusterTargets) == 0 {
|
||||
errMsg := "no policy (no targets found) to sync configs"
|
||||
klog.InfoS(errMsg, "project", project)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
policies := make([]v1beta1.AppPolicy, len(clusterTargets))
|
||||
for i, t := range clusterTargets {
|
||||
properties, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policies[i] = v1beta1.AppPolicy{
|
||||
Type: "topology",
|
||||
Name: t.Namespace,
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
scratch := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: project,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: "ref-objects",
|
||||
Properties: &runtime.RawExtension{Raw: objectsBytes},
|
||||
},
|
||||
},
|
||||
Policies: policies,
|
||||
},
|
||||
}
|
||||
return k8sClient.Create(ctx, scratch)
|
||||
}
|
||||
// config sync application exists, update it
|
||||
app.Spec.Components = []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: "ref-objects",
|
||||
Properties: &runtime.RawExtension{Raw: objectsBytes},
|
||||
},
|
||||
}
|
||||
currentTargets := make([]ApplicationDeployTarget, len(app.Spec.Policies))
|
||||
for i, p := range app.Spec.Policies {
|
||||
var t ApplicationDeployTarget
|
||||
if err := json.Unmarshal(p.Properties.Raw, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
currentTargets[i] = t
|
||||
}
|
||||
|
||||
mergedTarget := mergeTargets(currentTargets, targets)
|
||||
if len(mergedTarget) == 0 {
|
||||
errMsg := "no policy (no targets found) to sync configs"
|
||||
klog.InfoS(errMsg, "project", project)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
mergedPolicies := make([]v1beta1.AppPolicy, len(mergedTarget))
|
||||
for i, t := range mergedTarget {
|
||||
properties, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mergedPolicies[i] = v1beta1.AppPolicy{
|
||||
Type: "topology",
|
||||
Name: t.Namespace,
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
app.Spec.Policies = mergedPolicies
|
||||
return k8sClient.Update(ctx, app)
|
||||
}
|
||||
|
||||
func mergeTargets(currentTargets []ApplicationDeployTarget, targets []*model.ClusterTarget) []ApplicationDeployTarget {
|
||||
var (
|
||||
mergedTargets []ApplicationDeployTarget
|
||||
// make sure the clusters of target with same namespace are merged
|
||||
clusterTargets = convertClusterTargets(targets)
|
||||
)
|
||||
|
||||
for _, c := range currentTargets {
|
||||
var hasSameNamespace bool
|
||||
for _, t := range clusterTargets {
|
||||
if c.Namespace == t.Namespace {
|
||||
hasSameNamespace = true
|
||||
clusters := set.NewSetFromSlice(stringToInterfaceSlice(t.Clusters))
|
||||
for _, cluster := range c.Clusters {
|
||||
clusters.Add(cluster)
|
||||
}
|
||||
mergedTargets = append(mergedTargets, ApplicationDeployTarget{Namespace: c.Namespace,
|
||||
Clusters: interfaceToStringSlice(clusters.ToSlice())})
|
||||
}
|
||||
}
|
||||
if !hasSameNamespace {
|
||||
mergedTargets = append(mergedTargets, c)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range clusterTargets {
|
||||
var hasSameNamespace bool
|
||||
for _, c := range currentTargets {
|
||||
if c.Namespace == t.Namespace {
|
||||
hasSameNamespace = true
|
||||
}
|
||||
}
|
||||
if !hasSameNamespace {
|
||||
mergedTargets = append(mergedTargets, t)
|
||||
}
|
||||
}
|
||||
|
||||
return mergedTargets
|
||||
}
|
||||
|
||||
func convertClusterTargets(targets []*model.ClusterTarget) []ApplicationDeployTarget {
|
||||
type Target struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Clusters []interface{} `json:"clusters"`
|
||||
}
|
||||
|
||||
var (
|
||||
clusterTargets []Target
|
||||
namespaceSet = set.NewSet()
|
||||
)
|
||||
|
||||
for i := 0; i < len(targets); i++ {
|
||||
clusters := set.NewSet(targets[i].ClusterName)
|
||||
for j := i + 1; j < len(targets); j++ {
|
||||
if targets[i].Namespace == targets[j].Namespace {
|
||||
clusters.Add(targets[j].ClusterName)
|
||||
}
|
||||
}
|
||||
if namespaceSet.Contains(targets[i].Namespace) {
|
||||
continue
|
||||
}
|
||||
clusterTargets = append(clusterTargets, Target{
|
||||
Namespace: targets[i].Namespace,
|
||||
Clusters: clusters.ToSlice(),
|
||||
})
|
||||
namespaceSet.Add(targets[i].Namespace)
|
||||
}
|
||||
|
||||
t := make([]ApplicationDeployTarget, len(clusterTargets))
|
||||
for i, ct := range clusterTargets {
|
||||
t[i] = ApplicationDeployTarget{
|
||||
Namespace: ct.Namespace,
|
||||
Clusters: interfaceToStringSlice(ct.Clusters),
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func interfaceToStringSlice(i []interface{}) []string {
|
||||
var s []string
|
||||
for _, v := range i {
|
||||
s = append(s, v.(string))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func stringToInterfaceSlice(i []string) []interface{} {
|
||||
var s []interface{}
|
||||
for _, v := range i {
|
||||
s = append(s, v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// destroySyncConfigsApp will delete the application which is used to sync configs
|
||||
func destroySyncConfigsApp(ctx context.Context, k8sClient client.Client, project string) error {
|
||||
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
|
||||
var app = &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return k8sClient.Delete(ctx, app)
|
||||
}
|
||||
770
pkg/apiserver/rest/usecase/config_test.go
Normal file
770
pkg/apiserver/rest/usecase/config_test.go
Normal file
@@ -0,0 +1,770 @@
|
||||
/*
|
||||
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 usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/agiledragon/gomonkey/v2"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
)
|
||||
|
||||
func TestListConfigTypes(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
def1 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
definitionType: types.TerraformProvider,
|
||||
},
|
||||
},
|
||||
}
|
||||
def2 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
definitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
|
||||
|
||||
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
|
||||
return k8sClient, nil, nil
|
||||
})
|
||||
defer patches.Reset()
|
||||
|
||||
h := NewConfigUseCase(nil)
|
||||
|
||||
type args struct {
|
||||
h ConfigHandler
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configTypes []*apis.ConfigType
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configTypes: []*apis.ConfigType{
|
||||
{
|
||||
Name: "def2",
|
||||
Alias: "Def2",
|
||||
Definitions: []string{"def2"},
|
||||
},
|
||||
{
|
||||
Alias: "Terraform Cloud Provider",
|
||||
Name: types.TerraformProvider,
|
||||
Definitions: []string{
|
||||
"def1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.ListConfigTypes(ctx, "")
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configTypes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigType(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
def2 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
definitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def2).Build()
|
||||
|
||||
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
|
||||
return k8sClient, nil, nil
|
||||
})
|
||||
defer patches.Reset()
|
||||
|
||||
h := NewConfigUseCase(nil)
|
||||
|
||||
type args struct {
|
||||
h ConfigHandler
|
||||
name string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configType *apis.ConfigType
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
h: h,
|
||||
name: "def2",
|
||||
},
|
||||
want: want{
|
||||
configType: &apis.ConfigType{
|
||||
Alias: "Def2",
|
||||
Name: "def2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
args: args{
|
||||
h: h,
|
||||
name: "def99",
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to get config type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigType(ctx, tc.args.name)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateConfig(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
|
||||
|
||||
h := &configUseCaseImpl{kubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
h ConfigHandler
|
||||
req apis.CreateConfigRequest
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
properties, err := json.Marshal(map[string]interface{}{
|
||||
"name": "default",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "delete config when it's not ready",
|
||||
args: args{
|
||||
h: h,
|
||||
req: apis.CreateConfigRequest{
|
||||
Name: "a",
|
||||
ComponentType: "b",
|
||||
Project: "c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create terraform-alibaba config",
|
||||
args: args{
|
||||
h: h,
|
||||
req: apis.CreateConfigRequest{
|
||||
Name: "n1",
|
||||
ComponentType: "terraform-alibaba",
|
||||
Project: "p1",
|
||||
Properties: string(properties),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.args.h.CreateConfig(ctx, tc.args.req)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigs(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")
|
||||
|
||||
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",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsNotReady,
|
||||
},
|
||||
}
|
||||
|
||||
provider3 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider3",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider3",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRendering,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1).Build()
|
||||
|
||||
h := &configUseCaseImpl{kubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
configType string
|
||||
h ConfigHandler
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configs []*apis.Config
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
configType: types.TerraformProvider,
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apis.Config{
|
||||
{
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
},
|
||||
{
|
||||
Name: "provider2",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Not ready",
|
||||
},
|
||||
{
|
||||
Name: "provider3",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Not ready",
|
||||
ConfigType: "terraform-alibaba",
|
||||
ApplicationStatus: common.ApplicationRendering,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigs(ctx, tc.args.configType)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeTargets(t *testing.T) {
|
||||
currentTargets := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c1", "c2"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
},
|
||||
}
|
||||
targets := []*model.ClusterTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c4",
|
||||
}, {
|
||||
Namespace: "n1",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
{
|
||||
Namespace: "n2",
|
||||
ClusterName: "c3",
|
||||
},
|
||||
}
|
||||
|
||||
expected := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c1", "c2", "c5"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
}, {
|
||||
Namespace: "n3",
|
||||
Clusters: []string{"c4"},
|
||||
},
|
||||
}
|
||||
|
||||
got := mergeTargets(currentTargets, targets)
|
||||
|
||||
for i, g := range got {
|
||||
clusters := g.Clusters
|
||||
sort.SliceStable(clusters, func(i, j int) bool {
|
||||
return clusters[i] < clusters[j]
|
||||
})
|
||||
got[i].Clusters = clusters
|
||||
}
|
||||
assert.DeepEqual(t, expected, got)
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
targets := []*model.ClusterTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c4",
|
||||
}, {
|
||||
Namespace: "n1",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
{
|
||||
Namespace: "n2",
|
||||
ClusterName: "c3",
|
||||
},
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
}
|
||||
|
||||
expected := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
Clusters: []string{"c4", "c5"},
|
||||
},
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c5"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
},
|
||||
}
|
||||
|
||||
got := convertClusterTargets(targets)
|
||||
|
||||
for i, g := range got {
|
||||
clusters := g.Clusters
|
||||
sort.SliceStable(clusters, func(i, j int) bool {
|
||||
return clusters[i] < clusters[j]
|
||||
})
|
||||
got[i].Clusters = clusters
|
||||
}
|
||||
assert.DeepEqual(t, expected, got)
|
||||
}
|
||||
|
||||
func TestDestroySyncConfigsApp(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-p1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
}
|
||||
k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(app1).Build()
|
||||
|
||||
k8sClient2 := fake.NewClientBuilder().Build()
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
k8sClient client.Client
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"found": {
|
||||
args: args{
|
||||
project: "p1",
|
||||
k8sClient: k8sClient1,
|
||||
},
|
||||
},
|
||||
"not found": {
|
||||
args: args{
|
||||
project: "p1",
|
||||
k8sClient: k8sClient2,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "no kind is registered for the type v1beta1.Application",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := destroySyncConfigsApp(ctx, tc.args.k8sClient, tc.args.project)
|
||||
if err != nil || tc.want.errMsg != "" {
|
||||
if !strings.Contains(err.Error(), tc.want.errMsg) {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
secret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: "p1",
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
policies := []ApplicationDeployTarget{{
|
||||
Namespace: "n9",
|
||||
Clusters: []string{"c19"},
|
||||
}}
|
||||
properties, _ := json.Marshal(policies)
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-p2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "c19",
|
||||
Type: "topology",
|
||||
Properties: &runtime.RawExtension{Raw: properties},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(secret1, app1).Build()
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
targets []*model.ClusterTarget
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "create",
|
||||
args: args{
|
||||
project: "p1",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update",
|
||||
args: args{
|
||||
project: "p2",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skip config sync",
|
||||
args: args{
|
||||
project: "p3",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := SyncConfigs(ctx, k8sClient, tc.args.project, tc.args.targets)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteConfig(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p2",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
provider3 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p3",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-terraform-provider-p1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app2 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
normalApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a9",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1, app2, normalApp).Build()
|
||||
|
||||
h := &configUseCaseImpl{kubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
configType string
|
||||
name string
|
||||
h ConfigHandler
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "delete a legacy terraform provider",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p1",
|
||||
h: h,
|
||||
},
|
||||
want: want{},
|
||||
},
|
||||
{
|
||||
name: "delete a terraform provider",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p2",
|
||||
h: h,
|
||||
},
|
||||
want: want{},
|
||||
},
|
||||
{
|
||||
name: "delete a terraform provider, but its application not found",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p3",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "could not be disabled because it was created by enabling a Terraform provider or was manually created",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete a normal config, but failed",
|
||||
args: args{
|
||||
configType: "config-image-registry",
|
||||
name: "a10",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "not found",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.args.h.DeleteConfig(ctx, tc.args.configType, tc.args.name)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,57 +20,109 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
types2 "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
// NewHelmUsecase return a helmHandler
|
||||
func NewHelmUsecase() HelmHandler {
|
||||
c, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
log.Logger.Fatalf("get kube client failure %s", err.Error())
|
||||
}
|
||||
return defaultHelmHandler{
|
||||
helper: helm.NewHelperWithCache(),
|
||||
helper: helm.NewHelperWithCache(),
|
||||
k8sClient: c,
|
||||
}
|
||||
}
|
||||
|
||||
// HelmHandler responsible handle helm related interface
|
||||
type HelmHandler interface {
|
||||
ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error)
|
||||
ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error)
|
||||
GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error)
|
||||
ListChartNames(ctx context.Context, url string, secretName string, skipCache bool) ([]string, error)
|
||||
ListChartVersions(ctx context.Context, url string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error)
|
||||
GetChartValues(ctx context.Context, url string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error)
|
||||
ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error)
|
||||
}
|
||||
|
||||
type defaultHelmHandler struct {
|
||||
helper *helm.Helper
|
||||
helper *helm.Helper
|
||||
k8sClient client.Client
|
||||
}
|
||||
|
||||
func (d defaultHelmHandler) ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error) {
|
||||
charts, err := d.helper.ListChartsFromRepo(url, skipCache)
|
||||
func (d defaultHelmHandler) ListChartNames(ctx context.Context, repoURL string, secretName string, skipCache bool) ([]string, error) {
|
||||
if !utils.IsValidURL(repoURL) {
|
||||
return nil, bcode.ErrRepoInvalidURL
|
||||
}
|
||||
var opts *common.HTTPOption
|
||||
var err error
|
||||
if len(secretName) != 0 {
|
||||
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
|
||||
if err != nil {
|
||||
return nil, bcode.ErrRepoBasicAuth
|
||||
}
|
||||
}
|
||||
charts, err := d.helper.ListChartsFromRepo(repoURL, skipCache, opts)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("cannot fetch charts repo: %s, error: %s", url, err.Error())
|
||||
log.Logger.Errorf("cannot fetch charts repo: %s, error: %s", utils.Sanitize(repoURL), err.Error())
|
||||
return nil, bcode.ErrListHelmChart
|
||||
}
|
||||
return charts, nil
|
||||
}
|
||||
|
||||
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error) {
|
||||
chartVersions, err := d.helper.ListVersions(url, chartName, skipCache)
|
||||
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, repoURL string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error) {
|
||||
if !utils.IsValidURL(repoURL) {
|
||||
return nil, bcode.ErrRepoInvalidURL
|
||||
}
|
||||
var opts *common.HTTPOption
|
||||
var err error
|
||||
if len(secretName) != 0 {
|
||||
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
|
||||
if err != nil {
|
||||
return nil, bcode.ErrRepoBasicAuth
|
||||
}
|
||||
}
|
||||
chartVersions, err := d.helper.ListVersions(repoURL, chartName, skipCache, opts)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s error: %s", url, chartName, err.Error())
|
||||
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s error: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName), err.Error())
|
||||
return nil, bcode.ErrListHelmVersions
|
||||
}
|
||||
if len(chartVersions) == 0 {
|
||||
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s", url, chartName)
|
||||
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName))
|
||||
return nil, bcode.ErrChartNotExist
|
||||
}
|
||||
return chartVersions, nil
|
||||
}
|
||||
|
||||
func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error) {
|
||||
v, err := d.helper.GetValuesFromChart(url, chartName, version, skipCache)
|
||||
func (d defaultHelmHandler) GetChartValues(ctx context.Context, repoURL string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error) {
|
||||
if !utils.IsValidURL(repoURL) {
|
||||
return nil, bcode.ErrRepoInvalidURL
|
||||
}
|
||||
var opts *common.HTTPOption
|
||||
var err error
|
||||
if len(secretName) != 0 {
|
||||
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
|
||||
if err != nil {
|
||||
return nil, bcode.ErrRepoBasicAuth
|
||||
}
|
||||
}
|
||||
v, err := d.helper.GetValuesFromChart(repoURL, chartName, version, skipCache, opts)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("cannot fetch chart values repo: %s, chart: %s, version: %s, error: %s", url, chartName, version, err.Error())
|
||||
log.Logger.Errorf("cannot fetch chart values repo: %s, chart: %s, version: %s, error: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName), utils.Sanitize(version), err.Error())
|
||||
return nil, bcode.ErrGetChartValues
|
||||
}
|
||||
res := make(map[string]interface{}, len(v))
|
||||
@@ -78,6 +130,29 @@ func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, char
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d defaultHelmHandler) ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error) {
|
||||
var res []*v1.ChartRepoResponse
|
||||
var err error
|
||||
|
||||
projectSecrets := corev1.SecretList{}
|
||||
opts := []client.ListOption{
|
||||
client.MatchingLabels{oam.LabelConfigType: "config-helm-repository"},
|
||||
client.InNamespace(types.DefaultKubeVelaNS),
|
||||
}
|
||||
err = d.k8sClient.List(ctx, &projectSecrets, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range projectSecrets.Items {
|
||||
if config.ProjectMatched(item.DeepCopy(), projectName) {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
|
||||
}
|
||||
}
|
||||
|
||||
return &v1.ChartRepoResponseList{ChartRepoResponse: res}, nil
|
||||
}
|
||||
|
||||
// this func will flatten a nested map, the key will flatten with separator "." and the value's type will be keep
|
||||
// src is the map you want to flatten the output will be set in dest map
|
||||
// eg : src is {a:{b:{c:true}}} , the dest is {a.b.c:true}
|
||||
|
||||
@@ -17,9 +17,23 @@ limitations under the License.
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -37,6 +51,147 @@ func TestFlattenKeyFunc(t *testing.T) {
|
||||
assert.Equal(t, dstMap, res)
|
||||
}
|
||||
|
||||
var _ = Describe("Test helm repo list", func() {
|
||||
ctx := context.Background()
|
||||
var pSec, gSec v1.Secret
|
||||
|
||||
BeforeEach(func() {
|
||||
pSec = v1.Secret{}
|
||||
gSec = v1.Secret{}
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
|
||||
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &gSec)).Should(BeNil())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(k8sClient.Delete(ctx, &gSec)).Should(BeNil())
|
||||
Expect(k8sClient.Delete(ctx, &pSec)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test list with project ", func() {
|
||||
u := NewHelmUsecase()
|
||||
list, err := u.ListChartRepo(ctx, "my-project")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(2))
|
||||
found := 0
|
||||
for _, response := range list.ChartRepoResponse {
|
||||
if response.SecretName == "project-helm-repo" {
|
||||
Expect(response.URL).Should(BeEquivalentTo("https://kedacore.github.io/charts"))
|
||||
found++
|
||||
}
|
||||
if response.SecretName == "global-helm-repo" {
|
||||
Expect(response.URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
|
||||
found++
|
||||
}
|
||||
}
|
||||
Expect(found).Should(BeEquivalentTo(2))
|
||||
})
|
||||
|
||||
It("Test list func with not exist project", func() {
|
||||
u := NewHelmUsecase()
|
||||
list, err := u.ListChartRepo(ctx, "not-exist-project")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
|
||||
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
|
||||
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
|
||||
})
|
||||
|
||||
It("Test list func without project", func() {
|
||||
u := NewHelmUsecase()
|
||||
list, err := u.ListChartRepo(ctx, "")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
|
||||
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
|
||||
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("test helm usecasae", func() {
|
||||
ctx := context.Background()
|
||||
var repoSec v1.Secret
|
||||
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
|
||||
repoSec = v1.Secret{}
|
||||
Expect(yaml.Unmarshal([]byte(repoSecret), &repoSec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &repoSec)).Should(BeNil())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(k8sClient.Delete(ctx, &repoSec)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("helm associated usecase interface test", func() {
|
||||
var mockServer *httptest.Server
|
||||
|
||||
handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
u, p, ok := request.BasicAuth()
|
||||
if !ok || u != "admin" || p != "admin" {
|
||||
writer.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case request.URL.Path == "/index.yaml":
|
||||
index, err := ioutil.ReadFile("./testdata/helm/index.yaml")
|
||||
indexFile := string(index)
|
||||
indexFile = strings.ReplaceAll(indexFile, "server-url", mockServer.URL)
|
||||
if err != nil {
|
||||
writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
writer.Write([]byte(indexFile))
|
||||
|
||||
return
|
||||
case strings.Contains(request.URL.Path, "mysql-8.8.23.tgz"):
|
||||
pkg, err := ioutil.ReadFile("./testdata/helm/mysql-8.8.23.tgz")
|
||||
if err != nil {
|
||||
writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
writer.Write(pkg)
|
||||
return
|
||||
default:
|
||||
writer.Write([]byte("404 page not found"))
|
||||
}
|
||||
})
|
||||
|
||||
mockServer = httptest.NewServer(handler)
|
||||
|
||||
defer mockServer.Close()
|
||||
|
||||
u := NewHelmUsecase()
|
||||
charts, err := u.ListChartNames(ctx, mockServer.URL, "repo-secret", false)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(charts)).Should(BeEquivalentTo(1))
|
||||
Expect(charts[0]).Should(BeEquivalentTo("mysql"))
|
||||
|
||||
versions, err := u.ListChartVersions(ctx, mockServer.URL, "mysql", "repo-secret", false)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(versions)).Should(BeEquivalentTo(1))
|
||||
Expect(versions[0].Version).Should(BeEquivalentTo("8.8.23"))
|
||||
|
||||
values, err := u.GetChartValues(ctx, mockServer.URL, "mysql", "8.8.23", "repo-secret", false)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(values).ShouldNot(BeNil())
|
||||
Expect(len(values)).ShouldNot(BeEquivalentTo(0))
|
||||
})
|
||||
|
||||
It("coverage not secret notExist error", func() {
|
||||
u := NewHelmUsecase()
|
||||
_, err := u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
_, err = u.ListChartVersions(ctx, "http://127.0.0.1:8080", "mysql", "repo-secret-notExist", false)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
_, err = u.GetChartValues(ctx, "http://127.0.0.1:8080", "mysql", "8.8.23", "repo-secret-notExist", false)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var (
|
||||
src = `{
|
||||
"OAMSpecVer":"v0.2",
|
||||
@@ -175,4 +330,44 @@ var (
|
||||
"webhookService.port": 11443,
|
||||
"webhookService.type": "ClusterIP"
|
||||
}`
|
||||
globalSecret = `
|
||||
apiVersion: v1
|
||||
stringData:
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: ""
|
||||
name: global-helm-repo
|
||||
namespace: vela-system
|
||||
type: Opaque
|
||||
`
|
||||
projectSecret = `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: project-helm-repo
|
||||
namespace: vela-system
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: my-project
|
||||
stringData:
|
||||
url: https://kedacore.github.io/charts
|
||||
type: Opaque
|
||||
`
|
||||
repoSecret = `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: repo-secret
|
||||
namespace: vela-system
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: my-project-2
|
||||
stringData:
|
||||
username: admin
|
||||
password: admin
|
||||
type: Opaque
|
||||
`
|
||||
)
|
||||
|
||||
@@ -20,9 +20,16 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
@@ -46,6 +53,7 @@ type ProjectUsecase interface {
|
||||
DeleteProjectUser(ctx context.Context, projectName string, userName string) error
|
||||
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)
|
||||
}
|
||||
|
||||
type projectUsecaseImpl struct {
|
||||
@@ -290,7 +298,11 @@ func (p *projectUsecaseImpl) DeleteProject(ctx context.Context, name string) err
|
||||
return err
|
||||
}
|
||||
}
|
||||
return p.ds.Delete(ctx, &model.Project{Name: name})
|
||||
if err := p.ds.Delete(ctx, &model.Project{Name: name}); err != nil {
|
||||
return err
|
||||
}
|
||||
// delete config-sync application
|
||||
return destroySyncConfigsApp(ctx, p.k8sClient, name)
|
||||
}
|
||||
|
||||
// CreateProject create project
|
||||
@@ -466,6 +478,93 @@ func (p *projectUsecaseImpl) UpdateProjectUser(ctx context.Context, projectName
|
||||
return ConvertProjectUserModel2Base(&projectUser), nil
|
||||
}
|
||||
|
||||
func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error) {
|
||||
var (
|
||||
configs []*apisv1.Config
|
||||
legacyTerraformProviders []*apisv1.Config
|
||||
apps = &v1beta1.ApplicationList{}
|
||||
)
|
||||
if err := p.k8sClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if configType == types.TerraformProvider || configType == "" {
|
||||
// legacy providers
|
||||
var providers = &terraformapi.ProviderList{}
|
||||
if err := p.k8sClient.List(ctx, providers, client.InNamespace(types.DefaultAppNamespace)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range providers.Items {
|
||||
if p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig {
|
||||
continue
|
||||
}
|
||||
t := p.CreationTimestamp.Time
|
||||
var status = configIsNotReady
|
||||
if p.Status.State == terraformtypes.ProviderIsReady {
|
||||
status = configIsReady
|
||||
}
|
||||
legacyTerraformProviders = append(legacyTerraformProviders, &apisv1.Config{
|
||||
Name: p.Name,
|
||||
CreatedTime: &t,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
switch configType {
|
||||
case types.TerraformProvider:
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) ||
|
||||
!strings.Contains(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case "":
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if appProject != "" && appProject != projectName {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case types.DexConnector, types.HelmRepository, types.ImageRegistry:
|
||||
t := strings.ReplaceAll(configType, "config-", "")
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) {
|
||||
continue
|
||||
}
|
||||
if a.Labels[types.LabelConfigType] == t {
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported config type")
|
||||
}
|
||||
|
||||
for i, c := range configs {
|
||||
if c.ConfigType != "" {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
err := p.k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: c.ConfigType}, d)
|
||||
if err != nil {
|
||||
klog.InfoS("failed to get component definition", "ComponentDefinition", configType, "err", err)
|
||||
} else {
|
||||
configs[i].ConfigTypeAlias = d.Annotations[definitionAlias]
|
||||
}
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// ConvertProjectModel2Base convert project model to base struct
|
||||
func ConvertProjectModel2Base(project *model.Project, owner *model.User) *apisv1.ProjectBase {
|
||||
base := &apisv1.ProjectBase{
|
||||
@@ -492,3 +591,25 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser) *apisv1.ProjectUserBa
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv1.Config {
|
||||
var (
|
||||
applicationStatus = a.Status.Phase
|
||||
status string
|
||||
)
|
||||
if applicationStatus == common.ApplicationRunning {
|
||||
status = configIsReady
|
||||
} else {
|
||||
status = configIsNotReady
|
||||
}
|
||||
return &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: project,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: applicationStatus,
|
||||
Status: status,
|
||||
Alias: a.Annotations[types.AnnotationConfigAlias],
|
||||
Description: a.Annotations[types.AnnotationConfigDescription],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,23 @@ package usecase
|
||||
|
||||
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"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
@@ -155,6 +164,18 @@ var _ = Describe("Test project usecase functions", func() {
|
||||
Name: "test-project",
|
||||
Description: "this is a project description",
|
||||
}
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-test-project",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Type: "aaa",
|
||||
}},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
|
||||
_, err := projectUsecase.CreateProject(context.TODO(), req)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -223,6 +244,19 @@ var _ = Describe("Test project usecase functions", func() {
|
||||
Name: "test-project",
|
||||
Description: "this is a project description",
|
||||
}
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-test-project",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Type: "aaa",
|
||||
}},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
|
||||
|
||||
_, err := projectUsecase.CreateProject(context.TODO(), req)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -244,3 +278,222 @@ var _ = Describe("Test project usecase 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 := &projectUsecaseImpl{k8sClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
projectName string
|
||||
configType string
|
||||
h ProjectUsecase
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
pathName: "userName",
|
||||
},
|
||||
"applicationTemplate": {},
|
||||
"configs": {},
|
||||
},
|
||||
pathName: "projectName",
|
||||
},
|
||||
@@ -207,6 +208,14 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
"permission": {},
|
||||
"systemSetting": {},
|
||||
"definition": {},
|
||||
"configType": {
|
||||
pathName: "configType",
|
||||
subResources: map[string]resourceMetadata{
|
||||
"config": {
|
||||
pathName: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var existResourcePaths = convert(ResourceMaps)
|
||||
|
||||
@@ -18,31 +18,53 @@ package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// SystemInfoUsecase is usecase for systemInfoCollection
|
||||
type SystemInfoUsecase interface {
|
||||
Get(ctx context.Context) (*model.SystemInfo, error)
|
||||
GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error)
|
||||
UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error)
|
||||
Init(ctx context.Context) error
|
||||
}
|
||||
|
||||
type systemInfoUsecaseImpl struct {
|
||||
ds datastore.DataStore
|
||||
ds datastore.DataStore
|
||||
kubeClient client.Client
|
||||
}
|
||||
|
||||
// NewSystemInfoUsecase return a systemInfoCollectionUsecase
|
||||
func NewSystemInfoUsecase(ds datastore.DataStore) SystemInfoUsecase {
|
||||
return &systemInfoUsecaseImpl{ds: ds}
|
||||
kubecli, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
log.Logger.Fatalf("failed to get kube client: %s", err.Error())
|
||||
}
|
||||
return &systemInfoUsecaseImpl{ds: ds, kubeClient: kubecli}
|
||||
}
|
||||
|
||||
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
|
||||
func (u systemInfoUsecaseImpl) Get(ctx context.Context) (*model.SystemInfo, error) {
|
||||
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
|
||||
info := &model.SystemInfo{}
|
||||
entities, err := u.ds.List(ctx, info, &datastore.ListOptions{})
|
||||
@@ -54,21 +76,47 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
|
||||
if info.LoginType == "" {
|
||||
info.LoginType = model.LoginTypeLocal
|
||||
}
|
||||
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
|
||||
return info, nil
|
||||
}
|
||||
installID := rand.String(16)
|
||||
info.InstallID = installID
|
||||
info.EnableCollection = true
|
||||
info.LoginType = model.LoginTypeLocal
|
||||
info.BaseModel = model.BaseModel{CreateTime: time.Now()}
|
||||
err = u.ds.Add(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
|
||||
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
|
||||
info, err := u.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.SystemInfoResponse{
|
||||
SystemInfo: convertInfoToBase(info),
|
||||
SystemVersion: v1.SystemVersion{
|
||||
VelaVersion: version.VelaVersion,
|
||||
GitVersion: version.GitRevision,
|
||||
},
|
||||
StatisticInfo: v1.StatisticInfo{
|
||||
AppCount: info.StatisticInfo.AppCount,
|
||||
ClusterCount: info.StatisticInfo.ClusterCount,
|
||||
EnableAddonList: info.StatisticInfo.EnabledAddon,
|
||||
ComponentDefinitionTopList: info.StatisticInfo.TopKCompDef,
|
||||
TraitDefinitionTopList: info.StatisticInfo.TopKTraitDef,
|
||||
WorkflowDefinitionTopList: info.StatisticInfo.TopKWorkflowStepDef,
|
||||
PolicyDefinitionTopList: info.StatisticInfo.TopKPolicyDef,
|
||||
UpdateTime: info.StatisticInfo.UpdateTime,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error) {
|
||||
info, err := u.GetSystemInfo(ctx)
|
||||
info, err := u.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,10 +126,136 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
||||
LoginType: sysInfo.LoginType,
|
||||
BaseModel: model.BaseModel{
|
||||
CreateTime: info.CreateTime,
|
||||
}}
|
||||
UpdateTime: time.Now(),
|
||||
},
|
||||
StatisticInfo: info.StatisticInfo,
|
||||
}
|
||||
|
||||
if sysInfo.LoginType == model.LoginTypeDex {
|
||||
admin := &model.User{Name: model.DefaultAdminUserName}
|
||||
if err := u.ds.Get(ctx, admin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if admin.Email == "" {
|
||||
return nil, bcode.ErrEmptyAdminEmail
|
||||
}
|
||||
if err := generateDexConfig(ctx, u.kubeClient, sysInfo.VelaAddress, &modifiedInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = u.ds.Put(ctx, &modifiedInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.SystemInfoResponse{SystemInfo: modifiedInfo, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
|
||||
return &v1.SystemInfoResponse{
|
||||
SystemInfo: v1.SystemInfo{
|
||||
PlatformID: modifiedInfo.InstallID,
|
||||
EnableCollection: modifiedInfo.EnableCollection,
|
||||
LoginType: modifiedInfo.LoginType,
|
||||
// always use the initial createTime as system's installTime
|
||||
InstallTime: info.CreateTime,
|
||||
},
|
||||
SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u systemInfoUsecaseImpl) Init(ctx context.Context) error {
|
||||
info, err := u.Get(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedKey = info.InstallID
|
||||
_, err = initDexConfig(ctx, u.kubeClient, "http://velaux.com", &model.SystemInfo{})
|
||||
return err
|
||||
}
|
||||
|
||||
func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo {
|
||||
return v1.SystemInfo{
|
||||
PlatformID: info.InstallID,
|
||||
EnableCollection: info.EnableCollection,
|
||||
LoginType: info.LoginType,
|
||||
InstallTime: info.CreateTime,
|
||||
}
|
||||
}
|
||||
|
||||
func generateDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) error {
|
||||
secret, err := initDexConfig(ctx, kubeClient, velaAddress, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connectors, err := utils.GetDexConnectors(ctx, kubeClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(connectors) < 1 {
|
||||
return bcode.ErrNoDexConnector
|
||||
}
|
||||
config, err := model.NewJSONStructByStruct(info.DexConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*config)["connectors"] = connectors
|
||||
c, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !reflect.DeepEqual(secret.Data[secretDexConfigKey], c) {
|
||||
secret.Data[secretDexConfigKey] = c
|
||||
if err := kubeClient.Update(ctx, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDex(ctx, kubeClient); err != nil && !errors.Is(err, bcode.ErrDexNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) (*corev1.Secret, error) {
|
||||
dexConfig := model.DexConfig{
|
||||
Issuer: fmt.Sprintf("%s/dex", velaAddress),
|
||||
Web: model.DexWeb{
|
||||
HTTP: "0.0.0.0:5556",
|
||||
},
|
||||
Storage: model.DexStorage{
|
||||
Type: "memory",
|
||||
},
|
||||
StaticClients: []model.DexStaticClient{
|
||||
{
|
||||
ID: "velaux",
|
||||
Name: "Vela UX",
|
||||
Secret: "velaux-secret",
|
||||
RedirectURIs: []string{fmt.Sprintf("%s/callback", velaAddress)},
|
||||
},
|
||||
},
|
||||
EnablePasswordDB: true,
|
||||
}
|
||||
info.DexConfig = dexConfig
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
if err := kubeClient.Get(ctx, types.NamespacedName{
|
||||
Name: dexConfigName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
}, secret); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
config, err := yaml.Marshal(info.DexConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := kubeClient.Create(ctx, &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: dexConfigName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
secretDexConfigKey: config,
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
26
pkg/apiserver/rest/usecase/testdata/dex-config-def.yaml
vendored
Normal file
26
pkg/apiserver/rest/usecase/testdata/dex-config-def.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Dex config allow users to specify dex config in properties
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: dex-config
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import "encoding/yaml"
|
||||
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: name: context.name
|
||||
namespace: context.namespace
|
||||
type: "Opaque"
|
||||
stringData: "config.yaml": yaml.Marshal(parameter)
|
||||
}
|
||||
parameter: {}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
36
pkg/apiserver/rest/usecase/testdata/helm/index.yaml
vendored
Normal file
36
pkg/apiserver/rest/usecase/testdata/helm/index.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
apiVersion: v1
|
||||
entries:
|
||||
mysql:
|
||||
- annotations:
|
||||
category: Database
|
||||
apiVersion: v2
|
||||
appVersion: 8.0.28
|
||||
created: "2022-04-07T03:26:37.378966939Z"
|
||||
dependencies:
|
||||
- name: common
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
tags:
|
||||
- bitnami-common
|
||||
version: 1.x.x
|
||||
description: Chart to create a Highly available MySQL cluster
|
||||
digest: 96f79c6daba90fb40fc698979fab33f7a60987b1d23cd5080bc885129568a423
|
||||
home: https://github.com/bitnami/charts/tree/master/bitnami/mysql
|
||||
icon: https://bitnami.com/assets/stacks/mysql/img/mysql-stack-220x234.png
|
||||
keywords:
|
||||
- mysql
|
||||
- database
|
||||
- sql
|
||||
- cluster
|
||||
- high availability
|
||||
maintainers:
|
||||
- email: containers@bitnami.com
|
||||
name: Bitnami
|
||||
name: mysql
|
||||
sources:
|
||||
- https://github.com/bitnami/bitnami-docker-mysql
|
||||
- https://mysql.com
|
||||
urls:
|
||||
- server-url/mysql-8.8.23.tgz
|
||||
version: 8.8.23
|
||||
generated: "2022-04-07T03:26:37Z"
|
||||
serverInfo: {}
|
||||
BIN
pkg/apiserver/rest/usecase/testdata/helm/mysql-8.8.23.tgz
vendored
Normal file
BIN
pkg/apiserver/rest/usecase/testdata/helm/mysql-8.8.23.tgz
vendored
Normal file
Binary file not shown.
@@ -19,6 +19,8 @@ package usecase
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
@@ -82,7 +84,15 @@ func (u *userUsecaseImpl) Init(ctx context.Context) error {
|
||||
Name: admin,
|
||||
}); err != nil {
|
||||
if errors.Is(err, datastore.ErrRecordNotExist) {
|
||||
pwd := utils2.RandomString(8)
|
||||
pwd := func() string {
|
||||
p := utils2.RandomString(8)
|
||||
p += strconv.Itoa(rand.Intn(9)) // #nosec
|
||||
r := append([]rune(p), 'a'+rune(rand.Intn(26))) // #nosec
|
||||
rand.Shuffle(len(r), func(i, j int) { r[i], r[j] = r[j], r[i] })
|
||||
p = string(r)
|
||||
return p
|
||||
}()
|
||||
|
||||
encrypted, err := GeneratePasswordHash(pwd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,7 +106,7 @@ func (u *userUsecaseImpl) Init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
// print default password of admin user in log
|
||||
log.Logger.Infof("init admin user, password is %s", pwd)
|
||||
log.Logger.Infof("initialized admin username and password: admin / %s\n", pwd)
|
||||
secret := &corev1.Secret{}
|
||||
if err := u.k8sClient.Get(ctx, k8stypes.NamespacedName{
|
||||
Name: admin,
|
||||
@@ -184,7 +194,7 @@ func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error
|
||||
}
|
||||
}
|
||||
if err := u.ds.Delete(ctx, &model.User{Name: username}); err != nil {
|
||||
log.Logger.Errorf("failed to delete user", username, err.Error())
|
||||
log.Logger.Errorf("failed to delete user %s %v", utils2.Sanitize(username), err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -192,7 +202,7 @@ func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error
|
||||
|
||||
// CreateUser create user
|
||||
func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserRequest) (*apisv1.UserBase, error) {
|
||||
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
|
||||
sysInfo, err := u.sysUsecase.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -220,7 +230,7 @@ func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserR
|
||||
|
||||
// UpdateUser update user
|
||||
func (u *userUsecaseImpl) UpdateUser(ctx context.Context, user *model.User, req apisv1.UpdateUserRequest) (*apisv1.UserBase, error) {
|
||||
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
|
||||
sysInfo, err := u.sysUsecase.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,11 +25,14 @@ import (
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
@@ -74,7 +77,12 @@ var _ = Describe("Test application usecase function", func() {
|
||||
})
|
||||
|
||||
It("Test HandleApplicationWebhook function", func() {
|
||||
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
|
||||
var ns = corev1.Namespace{}
|
||||
ns.Name = types.DefaultKubeVelaNS
|
||||
err := k8sClient.Create(context.TODO(), &ns)
|
||||
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
_, err = projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
_, err = targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook", Project: "project-webhook"})
|
||||
|
||||
@@ -35,4 +35,8 @@ var (
|
||||
ErrInvalidLoginRequest = NewBcode(400, 12008, "the login request is invalid")
|
||||
// ErrInvalidDexConfig is the error of invalid dex config
|
||||
ErrInvalidDexConfig = NewBcode(400, 12009, "the dex config is invalid")
|
||||
// ErrRefreshTokenExpired is the error of refresh token expired
|
||||
ErrRefreshTokenExpired = NewBcode(400, 12010, "the refresh token is expired")
|
||||
// ErrNoDexConnector is the error of no dex connector
|
||||
ErrNoDexConnector = NewBcode(400, 12011, "there is no dex connector")
|
||||
)
|
||||
|
||||
@@ -30,3 +30,9 @@ var ErrChartNotExist = NewBcode(200, 13004, "this chart not exist in the reposit
|
||||
|
||||
// ErrSkipCacheParameter means the skip cache parameter miss config
|
||||
var ErrSkipCacheParameter = NewBcode(400, 13005, "skip cache parameter miss config, the value only can be true or false")
|
||||
|
||||
// ErrRepoBasicAuth means extract repo auth info from secret error
|
||||
var ErrRepoBasicAuth = NewBcode(400, 13006, "extract repo auth info from secret error")
|
||||
|
||||
// ErrRepoInvalidURL means user input url is invalid
|
||||
var ErrRepoInvalidURL = NewBcode(400, 13007, "user input repository url is invalid")
|
||||
|
||||
@@ -33,4 +33,8 @@ var (
|
||||
ErrUserInconsistentPassword = NewBcode(401, 14007, "the password is inconsistent with the user")
|
||||
// ErrUsernameNotExist is the error of username not exist
|
||||
ErrUsernameNotExist = NewBcode(401, 14008, "the username is not exist")
|
||||
// ErrDexNotFound is the error of dex not found
|
||||
ErrDexNotFound = NewBcode(200, 14009, "the dex is not found")
|
||||
// ErrEmptyAdminEmail is the error of empty admin email
|
||||
ErrEmptyAdminEmail = NewBcode(400, 14010, "the admin email is empty, please set the admin email before using sso login")
|
||||
)
|
||||
|
||||
52
pkg/apiserver/rest/utils/dex.go
Normal file
52
pkg/apiserver/rest/utils/dex.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
// GetDexConnectors returns the dex connectors for Dex connector controller
|
||||
func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[string]interface{}, error) {
|
||||
secrets := &v1.SecretList{}
|
||||
if err := k8sClient.List(ctx, secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{types.LabelConfigType: types.DexConnector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connectors := make([]map[string]interface{}, len(secrets.Items))
|
||||
for i, s := range secrets.Items {
|
||||
var data map[string]interface{}
|
||||
key := s.Labels[types.LabelConfigSubType]
|
||||
err := json.Unmarshal(s.Data[key], &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connectors[i] = map[string]interface{}{
|
||||
"type": s.Labels[types.LabelConfigSubType],
|
||||
"id": s.Name,
|
||||
"name": s.Name,
|
||||
"config": data,
|
||||
}
|
||||
}
|
||||
|
||||
return connectors, nil
|
||||
}
|
||||
106
pkg/apiserver/rest/utils/dex_test.go
Normal file
106
pkg/apiserver/rest/utils/dex_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
func TestGetDexConnectors(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
type args struct {
|
||||
k8sClient client.Client
|
||||
}
|
||||
type want struct {
|
||||
connectors []map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
ldap := map[string]interface{}{
|
||||
"clientID": "clientID",
|
||||
"clientSecret": "clientSecret",
|
||||
"callbackURL": "redirectURL",
|
||||
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
|
||||
}
|
||||
data, err := json.Marshal(ldap)
|
||||
assert.NoError(t, err)
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a",
|
||||
Namespace: "vela-system",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/source-of-truth": "from-inner-system",
|
||||
"config.oam.dev/catalog": "velacore-config",
|
||||
"config.oam.dev/type": "config-dex-connector",
|
||||
"config.oam.dev/sub-type": "ldap",
|
||||
"project": "abc",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"ldap": data,
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithObjects(secret).Build()
|
||||
|
||||
testcaes := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
|
||||
"test": {args: args{
|
||||
k8sClient: k8sClient,
|
||||
},
|
||||
want: want{
|
||||
connectors: []map[string]interface{}{{
|
||||
"id": "a",
|
||||
"name": "a",
|
||||
"type": "ldap",
|
||||
"config": map[string]interface{}{
|
||||
"clientID": "clientID",
|
||||
"clientSecret": "clientSecret",
|
||||
"callbackURL": "redirectURL",
|
||||
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testcaes {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := GetDexConnectors(ctx, tc.args.k8sClient)
|
||||
if err != tc.want.err {
|
||||
t.Errorf("%s: GetDexConnectors() error = %v, wantErr %v", name, err, tc.want.err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tc.want.connectors) {
|
||||
t.Errorf("%s: GetDexConnectors() = %v, want %v", name, got, tc.want.connectors)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ func (s *enabledAddonWebService) GetWebService() *restful.WebService {
|
||||
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
|
||||
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
|
||||
Returns(200, "OK", apis.ListAddonResponse{}).
|
||||
Returns(200, "OK", apis.ListEnabledAddonResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListAddonResponse{}))
|
||||
|
||||
|
||||
198
pkg/apiserver/rest/webservice/config.go
Normal file
198
pkg/apiserver/rest/webservice/config.go
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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 webservice
|
||||
|
||||
import (
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
// ConfigWebService returns config web service
|
||||
func ConfigWebService(u usecase.ConfigHandler, rbacUseCase usecase.RBACUsecase) WebService {
|
||||
return &configWebService{
|
||||
handler: u,
|
||||
rbacUseCase: rbacUseCase,
|
||||
}
|
||||
}
|
||||
|
||||
type configWebService struct {
|
||||
handler usecase.ConfigHandler
|
||||
rbacUseCase usecase.RBACUsecase
|
||||
}
|
||||
|
||||
func (s *configWebService) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/config_types").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for configuration management")
|
||||
|
||||
tags := []string{"config"}
|
||||
|
||||
ws.Route(ws.GET("/").To(s.listConfigTypes).
|
||||
Doc("list all config types").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("configType", "list")).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
|
||||
Returns(200, "OK", []apis.ConfigType{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}").To(s.getConfigType).
|
||||
Doc("get a config type").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("configType", "get")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Returns(200, "OK", apis.ConfigType{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.POST("/{configType}").To(s.createConfig).
|
||||
Doc("create or update a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("configType", "create")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Reads(apis.CreateConfigRequest{}).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Returns(404, "Not Found", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}/configs").To(s.getConfigs).
|
||||
Doc("get configs from a config type").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("config", "list")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}/configs/{name}").To(s.getConfig).
|
||||
Doc("get a config from a config type").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("config", "get")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{configType}/configs/{name}").To(s.deleteConfig).
|
||||
Doc("delete a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.rbacUseCase.CheckPerm("config", "delete")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Returns(404, "Not Found", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
func (s *configWebService) listConfigTypes(req *restful.Request, res *restful.Response) {
|
||||
types, err := s.handler.ListConfigTypes(req.Request.Context(), req.QueryParameter("query"))
|
||||
if len(types) == 0 && err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(types)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configWebService) getConfigType(req *restful.Request, res *restful.Response) {
|
||||
t, err := s.handler.GetConfigType(req.Request.Context(), req.PathParameter("configType"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(t)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configWebService) createConfig(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var createReq apis.CreateConfigRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := s.handler.CreateConfig(req.Request.Context(), createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configWebService) getConfigs(req *restful.Request, res *restful.Response) {
|
||||
configs, err := s.handler.GetConfigs(req.Request.Context(), req.PathParameter("configType"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(configs)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configWebService) getConfig(req *restful.Request, res *restful.Response) {
|
||||
t, err := s.handler.GetConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(t)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configWebService) deleteConfig(req *restful.Request, res *restful.Response) {
|
||||
err := s.handler.DeleteConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,21 @@ func (h helmWebService) GetWebService() *restful.WebService {
|
||||
|
||||
tags := []string{"repository", "helm"}
|
||||
|
||||
// List charts
|
||||
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")).
|
||||
Returns(200, "OK", []string{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
// List charts
|
||||
ws.Route(ws.GET("/charts").To(h.listCharts).
|
||||
Doc("list charts").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
|
||||
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
|
||||
Returns(200, "OK", []string{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
@@ -60,6 +70,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
|
||||
Doc("list versions").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
|
||||
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
|
||||
Returns(200, "OK", v1.ChartVersionListResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
@@ -69,6 +80,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
|
||||
Doc("get chart value").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
|
||||
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
|
||||
Returns(200, "OK", map[string]interface{}{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
@@ -79,12 +91,13 @@ func (h helmWebService) GetWebService() *restful.WebService {
|
||||
|
||||
func (h helmWebService) listCharts(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
secName := req.QueryParameter("secretName")
|
||||
skipCache, err := isSkipCache(req)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
|
||||
return
|
||||
}
|
||||
charts, err := h.usecase.ListChartNames(context.Background(), url, skipCache)
|
||||
charts, err := h.usecase.ListChartNames(context.Background(), url, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -99,13 +112,14 @@ func (h helmWebService) listCharts(req *restful.Request, res *restful.Response)
|
||||
func (h helmWebService) listVersions(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
chartName := req.PathParameter("chart")
|
||||
secName := req.QueryParameter("secretName")
|
||||
skipCache, err := isSkipCache(req)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, skipCache)
|
||||
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -119,6 +133,7 @@ func (h helmWebService) listVersions(req *restful.Request, res *restful.Response
|
||||
|
||||
func (h helmWebService) chartValues(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
secName := req.QueryParameter("secretName")
|
||||
chartName := req.PathParameter("chart")
|
||||
version := req.PathParameter("version")
|
||||
skipCache, err := isSkipCache(req)
|
||||
@@ -127,7 +142,7 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, skipCache)
|
||||
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -139,6 +154,20 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmWebService) listRepo(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
repos, err := h.usecase.ListChartRepo(context.Background(), project)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(repos)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isSkipCache(req *restful.Request) (bool, error) {
|
||||
skipStr := req.QueryParameter("skipCache")
|
||||
skipCache := false
|
||||
|
||||
@@ -176,6 +176,16 @@ func (n *projectWebService) GetWebService() *restful.WebService {
|
||||
Returns(200, "OK", []apis.PermissionBase{}).
|
||||
Writes([]apis.PermissionBase{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/configs").To(n.getConfigs).
|
||||
Doc("get configs which are in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.rbacUsecase.CheckPerm("project", "list")).
|
||||
Param(ws.QueryParameter("configType", "config type").DataType("string")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.Config{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
@@ -503,3 +513,23 @@ func (n *projectWebService) listProjectPermissions(req *restful.Request, res *re
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectWebService) getConfigs(req *restful.Request, res *restful.Response) {
|
||||
configs, err := n.projectUsecase.GetConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("configType"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if configs == nil {
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(configs)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,15 +73,16 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
|
||||
definitionUsecase := usecase.NewDefinitionUsecase()
|
||||
addonUsecase := usecase.NewAddonUsecase(addonCacheTime)
|
||||
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase, envUsecase)
|
||||
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
|
||||
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
|
||||
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
|
||||
helmUsecase := usecase.NewHelmUsecase()
|
||||
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase, rbacUsecase)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase, userUsecase)
|
||||
configUseCase := usecase.NewConfigUseCase(authenticationUsecase)
|
||||
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
|
||||
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
|
||||
// Modules that require default data initialization, Call it here in order
|
||||
if initDatabase {
|
||||
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase)
|
||||
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase, systemInfoUsecase)
|
||||
}
|
||||
|
||||
// Application
|
||||
@@ -95,6 +96,9 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
|
||||
RegisterWebService(NewEnabledAddonWebService(addonUsecase, rbacUsecase))
|
||||
RegisterWebService(NewAddonRegistryWebService(addonUsecase, rbacUsecase))
|
||||
|
||||
// Config management
|
||||
RegisterWebService(ConfigWebService(configUseCase, rbacUsecase))
|
||||
|
||||
// Resources
|
||||
RegisterWebService(NewClusterWebService(clusterUsecase, rbacUsecase))
|
||||
RegisterWebService(NewOAMApplication(oamApplicationUsecase, rbacUsecase))
|
||||
|
||||
@@ -66,6 +66,11 @@ func (c *CR2UX) initCache(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application, del bool) bool {
|
||||
|
||||
if targetApp != nil && targetApp.Labels != nil && targetApp.Labels[model.LabelSourceOfTruth] == model.FromInner {
|
||||
return false
|
||||
}
|
||||
|
||||
key := formatAppComposedName(targetApp.Name, targetApp.Namespace)
|
||||
cachedData, ok := c.cache.Load(key)
|
||||
if ok {
|
||||
@@ -85,9 +90,11 @@ func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application,
|
||||
|
||||
// This is a double check to make sure the app not be converted and un-deployed
|
||||
sot := c.CheckSoTFromAppMeta(ctx, targetApp.Name, targetApp.Namespace, CheckSoTFromCR(targetApp))
|
||||
|
||||
switch sot {
|
||||
case model.FromUX, model.FromInner:
|
||||
case model.FromUX:
|
||||
// we don't sync if the application is not created from CR
|
||||
return false
|
||||
case model.FromInner:
|
||||
// we don't sync if the application is not created from CR
|
||||
return false
|
||||
case model.FromCR:
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"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/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
@@ -95,5 +96,28 @@ var _ = Describe("Test Cache", func() {
|
||||
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(true))
|
||||
|
||||
})
|
||||
It("Test don't cache with from inner system label", func() {
|
||||
dbNamespace := "cache-db-ns2-test"
|
||||
|
||||
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: dbNamespace})
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
var ns = corev1.Namespace{}
|
||||
ns.Name = dbNamespace
|
||||
err = k8sClient.Create(context.TODO(), &ns)
|
||||
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
cr2ux := CR2UX{ds: ds, cli: k8sClient, cache: sync.Map{}}
|
||||
ctx := context.Background()
|
||||
|
||||
app1 := &v1beta1.Application{}
|
||||
app1.Name = "app1"
|
||||
app1.Namespace = dbNamespace
|
||||
app1.Generation = 1
|
||||
app1.Spec.Components = []common.ApplicationComponent{}
|
||||
app1.Labels = make(map[string]string)
|
||||
app1.Labels[model.LabelSourceOfTruth] = model.FromInner
|
||||
Expect(k8sClient.Create(ctx, app1)).Should(BeNil())
|
||||
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(false))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
@@ -49,7 +50,8 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
|
||||
model.LabelSourceOfTruth: model.FromCR,
|
||||
},
|
||||
}
|
||||
|
||||
appMeta.CreateTime = targetApp.CreationTimestamp.Time
|
||||
appMeta.UpdateTime = time.Now()
|
||||
// 1. convert app meta and env
|
||||
dsApp := &model.DataStoreApp{
|
||||
AppMeta: appMeta,
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"cuelang.org/go/cue/format"
|
||||
json2cue "cuelang.org/go/encoding/json"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -63,7 +63,9 @@ const (
|
||||
// WriteConnectionSecretToRefKey is used to create a secret for cloud resource connection
|
||||
WriteConnectionSecretToRefKey = "writeConnectionSecretToRef"
|
||||
// RegionKey is the region of a Cloud Provider
|
||||
RegionKey = "region"
|
||||
// It's used to override the region of a Cloud Provider
|
||||
// Refer to https://github.com/oam-dev/terraform-controller/blob/master/api/v1beta2/configuration_types.go#L66 for details
|
||||
RegionKey = "customRegion"
|
||||
// ProviderRefKey is the reference of a Provider
|
||||
ProviderRefKey = "providerRef"
|
||||
)
|
||||
@@ -666,7 +668,7 @@ func generateTerraformConfigurationWorkload(wl *Workload, ns string) (*unstructu
|
||||
}
|
||||
|
||||
configuration := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: wl.Name,
|
||||
Namespace: ns,
|
||||
@@ -679,8 +681,6 @@ func generateTerraformConfigurationWorkload(wl *Workload, ns string) (*unstructu
|
||||
switch wl.FullTemplate.Terraform.Type {
|
||||
case "hcl":
|
||||
configuration.Spec.HCL = wl.FullTemplate.Terraform.Configuration
|
||||
case "json":
|
||||
configuration.Spec.JSON = wl.FullTemplate.Terraform.Configuration
|
||||
case "remote":
|
||||
configuration.Spec.Remote = wl.FullTemplate.Terraform.Configuration
|
||||
configuration.Spec.Path = wl.FullTemplate.Terraform.Path
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -584,10 +584,6 @@ variable "password" {
|
||||
raw.Raw = data
|
||||
|
||||
workload := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "terraform.core.oam.dev/v1beta1",
|
||||
Kind: "Configuration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/appRevision": "v1",
|
||||
@@ -902,7 +898,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
writeConnectionSecretToRef *terraformtypes.SecretReference
|
||||
json string
|
||||
hcl string
|
||||
remote string
|
||||
params map[string]interface{}
|
||||
@@ -917,16 +912,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"json workload with secret": {
|
||||
args: args{
|
||||
|
||||
json: "abc",
|
||||
params: map[string]interface{}{"acl": "private",
|
||||
"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": ""}},
|
||||
writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss", Namespace: "default"},
|
||||
},
|
||||
want: want{err: nil}},
|
||||
|
||||
"valid hcl workload": {
|
||||
args: args{
|
||||
hcl: "abc",
|
||||
@@ -999,19 +984,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.json != "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{
|
||||
Configuration: tc.args.json,
|
||||
Type: "json",
|
||||
},
|
||||
}
|
||||
configSpec = terraformapi.ConfigurationSpec{
|
||||
JSON: tc.args.json,
|
||||
Variable: raw,
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.remote != "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{
|
||||
@@ -1025,7 +997,7 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.hcl == "" && tc.args.json == "" && tc.args.remote == "" {
|
||||
if tc.args.hcl == "" && tc.args.remote == "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{},
|
||||
}
|
||||
@@ -1061,7 +1033,7 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
|
||||
if err == nil {
|
||||
tfConfiguration := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
|
||||
Spec: configSpec,
|
||||
}
|
||||
|
||||
@@ -288,6 +288,30 @@ func (p *Parser) GenerateAppFileFromRevision(appRev *v1beta1.ApplicationRevision
|
||||
for k, v := range appRev.Spec.WorkflowStepDefinitions {
|
||||
appfile.RelatedWorkflowStepDefinitions[k] = v.DeepCopy()
|
||||
}
|
||||
|
||||
// add compatible code for upgrading to v1.3 as the workflow steps were not recorded before v1.2
|
||||
if len(appfile.RelatedWorkflowStepDefinitions) == 0 && len(appfile.WorkflowSteps) > 0 {
|
||||
ctx := context.Background()
|
||||
for _, workflowStep := range appfile.WorkflowSteps {
|
||||
if wftypes.IsBuiltinWorkflowStepType(workflowStep.Type) {
|
||||
continue
|
||||
}
|
||||
if _, found := appfile.RelatedWorkflowStepDefinitions[workflowStep.Type]; found {
|
||||
continue
|
||||
}
|
||||
def := &v1beta1.WorkflowStepDefinition{}
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get workflow step definition %s", workflowStep.Type)
|
||||
}
|
||||
appfile.RelatedWorkflowStepDefinitions[workflowStep.Type] = def
|
||||
}
|
||||
|
||||
appRev.Spec.WorkflowStepDefinitions = make(map[string]v1beta1.WorkflowStepDefinition)
|
||||
for name, def := range appfile.RelatedWorkflowStepDefinitions {
|
||||
appRev.Spec.WorkflowStepDefinitions[name] = *def
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range appRev.Spec.ScopeDefinitions {
|
||||
appfile.RelatedScopeDefinitions[k] = v.DeepCopy()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
@@ -506,3 +508,202 @@ var _ = Describe("Test appFile parser", func() {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test application parser", func() {
|
||||
var app v1beta1.Application
|
||||
var apprev v1beta1.ApplicationRevision
|
||||
var wsd v1beta1.WorkflowStepDefinition
|
||||
var expectedExceptAppfile *Appfile
|
||||
var mockClient test.MockClient
|
||||
|
||||
BeforeEach(func() {
|
||||
// prepare WorkflowStepDefinition
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/wsd.yaml", &wsd)).Should(BeNil())
|
||||
|
||||
// prepare verify data
|
||||
expectedExceptAppfile = &Appfile{
|
||||
Name: "backport-1-2-test-demo",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: "backport-1-2-test-demo",
|
||||
Type: "webservice",
|
||||
Params: map[string]interface{}{
|
||||
"image": "nginx",
|
||||
},
|
||||
FullTemplate: &Template{
|
||||
TemplateStr: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
cmd?: [...string]
|
||||
}`,
|
||||
},
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(1),
|
||||
},
|
||||
Template: `
|
||||
parameter: {
|
||||
// +usage=Specify the number of workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
// +patchStrategy=retainKeys
|
||||
patch: spec: replicas: parameter.replicas
|
||||
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
WorkflowSteps: []v1beta1.WorkflowStep{
|
||||
{
|
||||
Name: "apply",
|
||||
Type: "apply-application",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create mock client
|
||||
mockClient = test.MockClient{
|
||||
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
if strings.Contains(key.Name, "unknown") {
|
||||
return &errors2.StatusError{ErrStatus: metav1.Status{Reason: "NotFound", Message: "not found"}}
|
||||
}
|
||||
switch o := obj.(type) {
|
||||
case *v1beta1.ComponentDefinition:
|
||||
wd, err := util.UnMarshalStringToComponentDefinition(componenetDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*o = *wd
|
||||
case *v1beta1.TraitDefinition:
|
||||
td, err := util.UnMarshalStringToTraitDefinition(traitDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*o = *td
|
||||
case *v1beta1.WorkflowStepDefinition:
|
||||
*o = wsd
|
||||
case *v1beta1.ApplicationRevision:
|
||||
*o = apprev
|
||||
default:
|
||||
// skip
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
When("with apply-application workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev1.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 1", func() {
|
||||
|
||||
appfile, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
|
||||
Expect(len(appfile.WorkflowSteps) > 0 &&
|
||||
len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
|
||||
|
||||
Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
|
||||
this := appfile.RelatedWorkflowStepDefinitions
|
||||
that := appfile.AppRevision.Spec.WorkflowStepDefinitions
|
||||
for i, w := range this {
|
||||
thatW := that[i]
|
||||
if !reflect.DeepEqual(*w, thatW) {
|
||||
fmt.Printf("appfile wsd:%s apprev wsd%s", (*w).Name, thatW.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
When("with apply-application and apply-component build-in workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev2.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 2", func() {
|
||||
|
||||
appfile, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
|
||||
Expect(len(appfile.WorkflowSteps) > 0 &&
|
||||
len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
|
||||
|
||||
Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
|
||||
this := appfile.RelatedWorkflowStepDefinitions
|
||||
that := appfile.AppRevision.Spec.WorkflowStepDefinitions
|
||||
for i, w := range this {
|
||||
thatW := that[i]
|
||||
if !reflect.DeepEqual(*w, thatW) {
|
||||
fmt.Printf("appfile wsd:%s apprev wsd%s", (*w).Name, thatW.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
When("with unknown workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev3.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 3", func() {
|
||||
|
||||
_, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error() == "failed to get workflow step definition apply-application-unknown: not found").Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
26
pkg/appfile/testdata/backport-1-2/app.yaml
vendored
Normal file
26
pkg/appfile/testdata/backport-1-2/app.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-application
|
||||
status:
|
||||
latestRevision:
|
||||
name: backport-1-2-test-demo-v1
|
||||
revision: 1
|
||||
revisionHash: 38ddf4e721073703
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user