Compare commits

...

30 Commits

Author SHA1 Message Date
barnettZQG
f468814371 Feat: add uischema manage command (#3021)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-30 14:38:00 +08:00
barnettZQG
a1b1d4a6f8 Fix: trigger webbook bug (#3024)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-30 14:37:40 +08:00
Tianxin Dong
82453b45f5 Fix: fix webhook definition (#3022)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2021-12-30 10:11:19 +08:00
Tianxin Dong
72a00b57e6 Feat: add webhook token in application (#2970)
* Feat: add webhook token in application

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* resolve comments

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: change update cm to commit context

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* refactor the code

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix json merge

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* add create and update time

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2021-12-30 10:10:34 +08:00
Jamie
ff4b10f0ee Fix: op.#Task couldn't work, debug task.cue and fix bugs (#3005)
Signed-off-by: yunjiazhong <yunjiazhong@tencent.com>

Co-authored-by: yunjiazhong <yunjiazhong@tencent.com>
2021-12-29 19:26:27 +08:00
Somefive
746eb0dbe4 Feat: support logs for velaql (#3011)
* Feat: support logs for velaql

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>

* Feat: extend parameter

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2021-12-28 20:49:56 +08:00
Somefive
a33d1e488a Feat: gc process ignore cluster not exists (#3007)
* Feat: gc process ignore cluster not exists

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>

* Feat: gc process ignore cluster not exists

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2021-12-28 13:12:23 +08:00
Somefive
0d6173c1ca Fix: pending deprecated test (#3009)
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2021-12-28 13:12:09 +08:00
barnettZQG
00a80b9ecb Feat: add request logging for the apiserver (#3012)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-28 11:41:53 +08:00
barnettZQG
8284581e0c Feat: set GOPROXY build arg (#3004)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-28 10:26:07 +08:00
Tianxin Dong
8a5759949a Fix: fix notfication properties for schema (#3006) 2021-12-27 20:00:26 +08:00
Zheng Xi Zhou
820db96eae Fix: change Grafana service type and remove domain parameter (#2996)
* Fix: change Grafana service type and remove domain parameter

Use Grafana service's external IP to visit the dashboard and
remove the prameter domain

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* address CI issues

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix api issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix ci

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix ci

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-26 20:38:19 +08:00
barnettZQG
4d69027300 Feat: check target where create and update env (#3003)
* Feat: check target where create and update env

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: list applications by env

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-25 14:20:36 +08:00
wyike
5210800cac Fix: add registryName into addon list (#2993)
* Fix: add registryName into addon list

Signed-off-by: wangyike <wangyike_wyk@163.com>

* fix congig map

Signed-off-by: wangyike <wangyike_wyk@163.com>

* fix several comments

Signed-off-by: wangyike <wangyike_wyk@163.com>

* small fix

Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-25 12:37:23 +08:00
wyike
66881c13d3 Fix: more app info in addon status (#3000)
Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-25 11:01:59 +08:00
Jianbo Sun
5648c56cf5 Refactor: align velaux env and CLI env, they both use K8s namespace as (#2975)
* Refactor: use createOrUpdateNamespace as a common util function

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feat: add ENV webservice handelr

* Fix: fix Env usecase logic

* Feat: Add Delete Env API

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: filter empty addon data

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feat: split makefiels and make it clear

* Feat: add k8s utils test

* Feat: Add env update interface

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feat: change env implementation

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: minor fix

* Revert "Fix: minor fix"

This reverts commit 9cafefa65a.

* Fix: use appusecase as parameter

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Refactor: align CLI vela env with new env design

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: minor fix

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feat: add page index and alias of env

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: fix tests and licence header

* Fix: fix makefile and add default target

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: update build swagger.json

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: change update env api

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: list env with alias

* Feat: add log to env delete

* Fix: can not get app status

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: support update workflow and refactor code

* Fix: lint

* Fix: remove swagger check

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: fix cli vela delete

* Fix: update test

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: update test

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: app deploy unit test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: SortOrderDescending is not effective

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: e2e test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: support default project/target/env

* Fix: make test and add swagger

* Fix: use separated datasource for unit test

* Fix: app rollback bug

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: fix e2e test

* Fix: kubeapi driver sort bug

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: e2e test

* Fix: api e2e test

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: e2e test fix

* Fix: try fix e2e test

* Fix: api e2e test

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2021-12-25 10:36:54 +08:00
Min Kim
27252f32de idempotent conditional apiservice deletion (#2997)
Signed-off-by: yue9944882 <291271447@qq.com>
2021-12-24 18:36:21 +08:00
Somefive
775faee96f Chore: add welcome logo in helm install notes (#2994)
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2021-12-24 16:15:28 +08:00
qiaozp
e818921a87 Fix: return reasonable error make multi-registry work (#2995)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2021-12-24 16:06:07 +08:00
wyike
551992e8f2 fix panic bug of addon enable (#2985)
Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-24 11:12:26 +08:00
wyike
e519c6142a Chore: delete useless test (#2984)
* delete useless test

Signed-off-by: wangyike <wangyike_wyk@163.com>

* delete healthscope related test

Signed-off-by: wangyike <wangyike_wyk@163.com>

* small fix

Signed-off-by: wangyike <wangyike_wyk@163.com>

* remove useless report

Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-23 18:13:00 +08:00
wyike
3198693ad7 Fix: cli addon registry style small fix (#2980)
* small fix

Signed-off-by: wangyike <wangyike_wyk@163.com>

* fix comments

Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-23 14:37:22 +08:00
Tianxin Dong
88aa6c0e83 Fix: change update cm to commit context (#2979)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2021-12-23 11:45:56 +08:00
Zheng Xi Zhou
8b82a79d1d Fix: vela port-forward supports Addon Observability (#2977)
* Fix: vela port-forward supports Addon Observability

Support port forwarding service of Addon Observability in
multiple clusters

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix CI issues

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-22 21:08:00 +08:00
wyike
14a57fc656 vela cli support oss path and cli upgrade an addon (#2976)
Signed-off-by: wangyike <wangyike_wyk@163.com>
2021-12-22 19:31:20 +08:00
Zheng Xi Zhou
d7ee46134d Fix: lowercase the key name in API response (#2978) 2021-12-22 18:14:34 +08:00
Jian.Li
6e5e26c19d fix message invalid (#2968)
Signed-off-by: Jian.Li <lj176172@alibaba-inc.com>
2021-12-22 12:47:27 +08:00
Zheng Xi Zhou
8750fc8fab Fix: implement addons/observability/status API (#2966)
* Fix: implement addons/observability/status API

Return all domains and the IPs from all clusters. And
provider the way to visit the console of observability

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* add unit tests

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-22 10:54:46 +08:00
qiaozp
7ed293e27e Fix: list addon will always return non-nil err (#2967)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2021-12-22 09:28:10 +08:00
Somefive
36ad77493c Fix: resource policy test flaky (#2969)
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2021-12-22 09:27:21 +08:00
164 changed files with 6447 additions and 6204 deletions

View File

@@ -90,12 +90,12 @@ jobs:
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: /tmp/e2e-profile.out,/tmp/oam-e2e-profile.out
files: /tmp/e2e-profile.out
flags: e2e-rollout-tests
name: codecov-umbrella
- name: Clean e2e profile
run: rm /tmp/e2e-profile.out /tmp/oam-e2e-profile.out
run: rm /tmp/e2e-profile.out
- name: Cleanup image
if: ${{ always() }}

View File

@@ -96,12 +96,12 @@ jobs:
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: /tmp/e2e-profile.out,/tmp/oam-e2e-profile.out
files: /tmp/e2e-profile.out
flags: e2etests
name: codecov-umbrella
- name: Clean e2e profile
run: rm /tmp/e2e-profile.out /tmp/oam-e2e-profile.out
run: rm /tmp/e2e-profile.out
- name: Cleanup image
if: ${{ always() }}

View File

@@ -33,52 +33,6 @@ jobs:
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false
compatibility-test:
runs-on: ubuntu-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
submodules: true
- name: Cache Go Dependencies
uses: actions/cache@v2
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Install ginkgo
run: |
sudo apt-get install -y golang-ginkgo-dev
- name: Setup Kind Cluster
uses: engineerd/setup-kind@v0.5.0
with:
version: ${{ env.KIND_VERSION }}
- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1.2
with:
version: 3.1.0
kubebuilderOnly: false
kubernetesVersion: v1.21.2
- name: Run Make compatibility-test
run: make compatibility-test
- name: Clean up testdata
run: make compatibility-testdata-cleanup
staticcheck:
runs-on: ubuntu-20.04
needs: detect-noop

View File

@@ -57,7 +57,7 @@ jobs:
- name: Build & Pushing vela-core for ACR
run: |
docker build -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }} .
docker build --build-arg GOPROXY=https://proxy.golang.org -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
@@ -72,13 +72,14 @@ jobs:
build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
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 }}
- name: Build & Pushing vela-apiserver for ACR
run: |
docker build -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }} -f Dockerfile.apiserver .
docker build --build-arg GOPROXY=https://proxy.golang.org -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
@@ -93,13 +94,14 @@ jobs:
build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
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 }}
- name: Build & Pushing vela runtime rollout for ACR
run: |
docker build -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }} .
docker build --build-arg GOPROXY=https://proxy.golang.org -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
@@ -114,6 +116,7 @@ jobs:
build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
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 }}

View File

@@ -8,7 +8,8 @@ COPY go.mod go.mod
COPY go.sum go.sum
# It's a proxy for CN developer, please unblock it if you have network issue
# RUN go env -w GOPROXY=https://goproxy.cn,direct
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://goproxy.cn}
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer

268
Makefile
View File

@@ -1,51 +1,11 @@
SHELL := /bin/bash
# Vela version
VELA_VERSION ?= master
# Repo info
GIT_COMMIT ?= git-$(shell git rev-parse --short HEAD)
GIT_COMMIT_LONG ?= $(shell git rev-parse HEAD)
VELA_VERSION_KEY := github.com/oam-dev/kubevela/version.VelaVersion
VELA_GITVERSION_KEY := github.com/oam-dev/kubevela/version.GitRevision
LDFLAGS ?= "-s -w -X $(VELA_VERSION_KEY)=$(VELA_VERSION) -X $(VELA_GITVERSION_KEY)=$(GIT_COMMIT)"
GOBUILD_ENV = GO111MODULE=on CGO_ENABLED=0
GOX = go run github.com/mitchellh/gox
TARGETS := darwin/amd64 linux/amd64 windows/amd64
DIST_DIRS := find * -type d -exec
TIME_LONG = `date +%Y-%m-%d' '%H:%M:%S`
TIME_SHORT = `date +%H:%M:%S`
TIME = $(TIME_SHORT)
BLUE := $(shell printf "\033[34m")
YELLOW := $(shell printf "\033[33m")
RED := $(shell printf "\033[31m")
GREEN := $(shell printf "\033[32m")
CNone := $(shell printf "\033[0m")
INFO = echo ${TIME} ${BLUE}[ .. ]${CNone}
WARN = echo ${TIME} ${YELLOW}[WARN]${CNone}
ERR = echo ${TIME} ${RED}[FAIL]${CNone}
OK = echo ${TIME} ${GREEN}[ OK ]${CNone}
FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# Image URL to use all building/pushing image targets
VELA_CORE_IMAGE ?= vela-core:latest
VELA_CORE_TEST_IMAGE ?= vela-core-test:$(GIT_COMMIT)
VELA_APISERVER_IMAGE ?= apiserver:latest
VELA_RUNTIME_ROLLOUT_IMAGE ?= vela-runtime-rollout:latest
VELA_RUNTIME_ROLLOUT_TEST_IMAGE ?= vela-runtime-rollout-test:$(GIT_COMMIT)
RUNTIME_CLUSTER_CONFIG ?= /tmp/worker.kubeconfig
RUNTIME_CLUSTER_NAME ?= worker
include makefiles/const.mk
include makefiles/dependency.mk
include makefiles/release.mk
include makefiles/develop.mk
include makefiles/build.mk
include makefiles/e2e.mk
.DEFAULT_GOAL := all
all: build
# Run tests
@@ -62,51 +22,9 @@ unit-test-apiserver:
build: fmt vet lint staticcheck vela-cli kubectl-vela
@$(OK) build succeed
vela-cli:
$(GOBUILD_ENV) go build -o bin/vela -a -ldflags $(LDFLAGS) ./references/cmd/cli/main.go
kubectl-vela:
$(GOBUILD_ENV) go build -o bin/kubectl-vela -a -ldflags $(LDFLAGS) ./cmd/plugin/main.go
doc-gen:
rm -r docs/en/cli/*
go run hack/docgen/gen.go
cross-build:
rm -rf _bin
go get github.com/mitchellh/gox@v0.4.0
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/vela/{{.OS}}-{{.Arch}}/vela" -osarch='$(TARGETS)' ./references/cmd/cli
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/kubectl-vela/{{.OS}}-{{.Arch}}/kubectl-vela" -osarch='$(TARGETS)' ./cmd/plugin
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/apiserver/{{.OS}}-{{.Arch}}/apiserver" -osarch="$(TARGETS)" ./cmd/apiserver
build-cleanup:
rm -rf _bin
compress:
( \
echo "\n## Release Info\nVERSION: $(VELA_VERSION)" >> README.md && \
echo "GIT_COMMIT: $(GIT_COMMIT_LONG)\n" >> README.md && \
cd _bin/vela && \
$(DIST_DIRS) cp ../../LICENSE {} \; && \
$(DIST_DIRS) cp ../../README.md {} \; && \
$(DIST_DIRS) tar -zcf vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r vela-{}.zip {} \; && \
cd ../kubectl-vela && \
$(DIST_DIRS) cp ../../LICENSE {} \; && \
$(DIST_DIRS) cp ../../README.md {} \; && \
$(DIST_DIRS) tar -zcf kubectl-vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r kubectl-vela-{}.zip {} \; && \
cd .. && \
sha256sum vela/vela-* kubectl-vela/kubectl-vela-* > sha256sums.txt \
)
# Run against the configured Kubernetes cluster in ~/.kube/config
run:
go run ./cmd/core/main.go --application-revision-limit 5
run-apiserver:
go run ./cmd/apiserver/main.go
# Run go fmt against code
fmt: goimports installcue
go fmt ./...
@@ -136,93 +54,14 @@ check-diff: reviewable
git diff --quiet || ($(ERR) please run 'make reviewable' to include all changes && false)
@$(OK) branch is clean
# Build the docker image
docker-build: docker-build-core docker-build-apiserver
@$(OK)
docker-build-core:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_CORE_IMAGE) .
docker-build-apiserver:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_APISERVER_IMAGE) -f Dockerfile.apiserver .
# Build the runtime docker image
docker-build-runtime-rollout:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_RUNTIME_ROLLOUT_IMAGE) -f runtime/rollout/Dockerfile .
# Push the docker image
docker-push:
docker push $(VELA_CORE_IMAGE)
e2e-setup-core:
sh ./hack/e2e/modify_charts.sh
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set applicationRevisionLimit=5 --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
kubectl wait --for=condition=Available deployment/kubevela-vela-core -n vela-system --timeout=180s
go run ./e2e/addon/mock &
setup-runtime-e2e-cluster:
helm upgrade --install --create-namespace --namespace vela-system --kubeconfig=$(RUNTIME_CLUSTER_CONFIG) --set image.pullPolicy=IfNotPresent --set image.repository=vela-runtime-rollout-test --set image.tag=$(GIT_COMMIT) --wait vela-rollout ./runtime/rollout/charts
e2e-setup:
helm install kruise https://github.com/openkruise/kruise/releases/download/v0.9.0/kruise-chart.tgz --set featureGates="PreDownloadImageForInPlaceUpdate=true"
sh ./hack/e2e/modify_charts.sh
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set applicationRevisionLimit=5 --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
helm upgrade --install --create-namespace --namespace oam-runtime-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait oam-runtime ./charts/oam-runtime
go run ./e2e/addon/mock &
bin/vela addon enable fluxcd
bin/vela addon enable terraform
bin/vela addon enable terraform-alibaba ALICLOUD_ACCESS_KEY=xxx ALICLOUD_SECRET_KEY=yyy ALICLOUD_REGION=cn-beijing
ginkgo version
ginkgo -v -r e2e/setup
timeout 600s bash -c -- 'while true; do kubectl get ns flux-system; if [ $$? -eq 0 ] ; then break; else sleep 5; fi;done'
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
kubectl wait --for=condition=Ready pod -l app=source-controller -n flux-system --timeout=600s
kubectl wait --for=condition=Ready pod -l app=helm-controller -n flux-system --timeout=600s
build-swagger:
go run ./cmd/apiserver/main.go build-swagger ./docs/apidoc/swagger.json
e2e-api-test:
# Run e2e test
ginkgo -v -skipPackage capability,setup,application -r e2e
ginkgo -v -r e2e/application
e2e-apiserver-test: build-swagger
go run ./e2e/addon/mock &
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
@$(OK) tests pass
e2e-test:
# Run e2e test
ginkgo -v --skip="rollout related e2e-test." ./test/e2e-test
@$(OK) tests pass
e2e-addon-test:
cp bin/vela /tmp/
ginkgo -v ./test/e2e-addon-test
@$(OK) tests pass
e2e-rollout-test:
ginkgo -v --focus="rollout related e2e-test." ./test/e2e-test
@$(OK) tests pass
e2e-multicluster-test:
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_multicluster_test.out ./test/e2e-multicluster-test
@$(OK) tests pass
compatibility-test: vet lint staticcheck generate-compatibility-testdata
# Run compatibility test with old crd
COMPATIBILITY_TEST=TRUE go test -race $(shell go list ./pkg/... | grep -v apiserver)
@$(OK) compatibility-test pass
generate-compatibility-testdata:
mkdir -p ./test/compatibility-test/testdata
go run ./test/compatibility-test/convert/main.go ./charts/vela-core/crds ./test/compatibility-test/testdata
compatibility-testdata-cleanup:
rm -f ./test/compatibility-test/testdata/*
e2e-cleanup:
# Clean up
rm -rf ~/.vela
image-cleanup:
ifneq (, $(shell which docker))
@@ -238,11 +77,7 @@ endif
endif
end-e2e-core:
sh ./hack/e2e/end_e2e_core.sh
end-e2e:
sh ./hack/e2e/end_e2e.sh
# load docker image to the kind cluster
kind-load:
@@ -267,24 +102,6 @@ manager: fmt vet lint manifests
vela-runtime-rollout-manager: fmt vet lint manifests
$(GOBUILD_ENV) go build -o ./runtime/rollout/bin/manager -a -ldflags $(LDFLAGS) ./runtime/rollout/cmd/main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
core-run: fmt vet manifests
go run ./cmd/core/main.go
# Run against the configured Kubernetes cluster in ~/.kube/config with debug logs
core-debug-run: fmt vet manifests
go run ./cmd/core/main.go --log-debug=true
# Install CRDs and Definitions of Vela Core into a cluster, this is for develop convenient.
core-install: manifests
kubectl apply -f hack/namespace.yaml
kubectl apply -f charts/vela-core/crds/
@$(OK) install succeed
# Uninstall CRDs and Definitions of Vela Core from a cluster, this is for develop convenient.
core-uninstall: manifests
kubectl delete -f charts/vela-core/crds/
# Generate manifests e.g. CRD, RBAC etc.
manifests: installcue kustomize
go generate $(foreach t,pkg apis,./$(t)/...)
@@ -295,82 +112,13 @@ manifests: installcue kustomize
rm -f config/crd/base/*
./vela-templates/gen_definitions.sh
GOLANGCILINT_VERSION ?= v1.38.0
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
HOSTARCH := $(shell uname -m)
ifeq ($(HOSTARCH),x86_64)
HOSTARCH := amd64
endif
golangci:
ifneq ($(shell which golangci-lint),)
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(shell which golangci-lint)
else ifeq (, $(shell which $(GOBIN)/golangci-lint))
@{ \
set -e ;\
echo 'installing golangci-lint-$(GOLANGCILINT_VERSION)' ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) $(GOLANGCILINT_VERSION) ;\
echo 'Successfully installed' ;\
}
GOLANGCILINT=$(GOBIN)/golangci-lint
else
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(GOBIN)/golangci-lint
endif
.PHONY: staticchecktool
staticchecktool:
ifeq (, $(shell which staticcheck))
@{ \
set -e ;\
echo 'installing honnef.co/go/tools/cmd/staticcheck ' ;\
GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck ;\
}
STATICCHECK=$(GOBIN)/staticcheck
else
STATICCHECK=$(shell which staticcheck)
endif
.PHONY: goimports
goimports:
ifeq (, $(shell which goimports))
@{ \
set -e ;\
GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports ;\
}
GOIMPORTS=$(GOBIN)/goimports
else
GOIMPORTS=$(shell which goimports)
endif
.PHONY: installcue
installcue:
ifeq (, $(shell which cue))
@{ \
set -e ;\
GO111MODULE=off go get -u cuelang.org/go/cmd/cue ;\
}
CUE=$(GOBIN)/cue
else
CUE=$(shell which cue)
endif
KUSTOMIZE_VERSION ?= 3.8.2
.PHONY: kustomize
kustomize:
ifeq (, $(shell kustomize version | grep $(KUSTOMIZE_VERSION)))
@{ \
set -eo pipefail ;\
echo 'installing kustomize-v$(KUSTOMIZE_VERSION) into $(GOBIN)' ;\
curl -sS https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash -s $(KUSTOMIZE_VERSION) $(GOBIN);\
echo 'Install succeed' ;\
}
KUSTOMIZE=$(GOBIN)/kustomize
else
KUSTOMIZE=$(shell which kustomize)
endif
check-license-header:
./hack/licence/header-check.sh

View File

@@ -64,8 +64,7 @@ type Config map[string]string
type EnvMeta struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Current string `json:"current,omitempty"`
Current string `json:"current"`
}
const (
@@ -83,6 +82,8 @@ const (
TypeDefinition = "Managing Definitions"
// TypePlugin defines one category used in Kubectl Plugin
TypePlugin = "Plugin Command"
// TypeUISchema defines one category
TypeUISchema = "Managing UISchema"
)
// LabelArg is the argument `label` of a definition

View File

@@ -1 +1,29 @@
Welcome to use the KubeVela! Enjoy your shipping application journey!
Welcome to use the KubeVela! Enjoy your shipping application journey!
,
//,
////
./ /////*
,/// ///////
.///// ////////
/////// /////////
//////// //////////
,///////// ///////////
,////////// ///////////.
./////////// ////////////
//////////// ////////////.
*//////////// ////////////*
#@@@@@@@@@@@* ..,,***/ /////////////
/@@@@@@@@@@@#
*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&
.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.&@@@* *@@@& ,@@@&.
_ __ _ __ __ _
| |/ /_ _ | |__ ___\ \ / /___ | | __ _
| ' /| | | || '_ \ / _ \\ \ / // _ \| | / _` |
| . \| |_| || |_) || __/ \ V /| __/| || (_| |
|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|

View File

@@ -1,5 +1,4 @@
{{ if .Values.multicluster.enabled }}
{{ if not (lookup "apps/v1" "Deployment" .Release.Namespace (printf "%s-cluster-gateway" .Release.Name)) }}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -73,7 +72,6 @@ spec:
maxSurge: 1
maxUnavailable: 1
{{ end }}
{{ end }}
---
{{ if .Values.multicluster.enabled }}
apiVersion: v1
@@ -91,7 +89,10 @@ spec:
{{ end }}
---
{{ if .Values.multicluster.enabled }}
{{ if not (lookup "apiregistration.k8s.io/v1" "APIService" "" "v1alpha1.cluster.core.oam.dev") }}
{{ $apiSvc := (lookup "apiregistration.k8s.io/v1" "APIService" "" "v1alpha1.cluster.core.oam.dev") }}
{{ $shouldAdopt := (not $apiSvc) }}
{{ if not $shouldAdopt }}{{ $shouldAdopt = (index ($apiSvc).metadata.annotations "meta.helm.sh/release-name") }}{{ end }}
{{ if $shouldAdopt }}
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:

View File

@@ -19,7 +19,16 @@ spec:
parameter: {
dingding?: {
// +usage=Specify the the dingding url, you can either sepcify it in value or use secretRef
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +useage=Specify the message that you want to sent
message: {
text?: *null | {
@@ -66,7 +75,16 @@ spec:
slack?: {
// +usage=Specify the the slack url, you can either sepcify it in value or use secretRef
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +useage=Specify the message that you want to sent
message: {
text: string
@@ -88,7 +106,16 @@ spec:
// +usage=The alias is the email alias to show after sending the email
alias?: string
// +usage=Specify the password of the email, you can either sepcify it in value or use secretRef
password: value | secretRef
password: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +usage=Specify the host of your email
host: string
// +usage=Specify the port of the email host, default to 587
@@ -150,13 +177,6 @@ spec:
description?: textType
url?: string
}
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
value: string
// send webhook notification
ding: op.#Steps & {
if parameter.dingding != _|_ {

View File

@@ -68,14 +68,18 @@ spec:
} @step(8)
}
}
secretRef: {
name: string
key: string
}
value: string
parameter: {
// +usage=Specify the webhook url
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +usage=Specify the data you want to send
data?: {...}
}

View File

@@ -494,7 +494,12 @@ spec:
replica: strconv.FormatInt(context.output.status.readyReplicas, 10)
}
}
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
if context.output.status.replicas != _|_ {
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
}
if context.output.status.replicas == _|_ {
message: ""
}
healthPolicy: |-
ready: {
if context.output.status.readyReplicas == _|_ {
@@ -504,7 +509,12 @@ spec:
replica: context.output.status.readyReplicas
}
}
isHealth: context.output.status.replicas == context.output.status.readyReplicas
if context.output.status.replicas != _|_ {
isHealth: context.output.status.replicas == ready.replica
}
if context.output.status.replicas == _|_ {
isHealth: false
}
workload:
definition:
apiVersion: apps/v1

View File

@@ -405,7 +405,12 @@ spec:
replica: strconv.FormatInt(context.output.status.readyReplicas, 10)
}
}
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
if context.output.status.replicas != _|_ {
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
}
if context.output.status.replicas == _|_ {
message: ""
}
healthPolicy: |-
ready: {
if context.output.status.readyReplicas == _|_ {
@@ -415,7 +420,12 @@ spec:
replica: context.output.status.readyReplicas
}
}
isHealth: context.output.status.replicas == context.output.status.readyReplicas
if context.output.status.replicas != _|_ {
isHealth: context.output.status.replicas == ready.replica
}
if context.output.status.replicas == _|_ {
isHealth: false
}
workload:
definition:
apiVersion: apps/v1

View File

@@ -19,7 +19,16 @@ spec:
parameter: {
dingding?: {
// +usage=Specify the the dingding url, you can either sepcify it in value or use secretRef
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +useage=Specify the message that you want to sent
message: {
text?: *null | {
@@ -66,7 +75,16 @@ spec:
slack?: {
// +usage=Specify the the slack url, you can either sepcify it in value or use secretRef
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +useage=Specify the message that you want to sent
message: {
text: string
@@ -88,7 +106,16 @@ spec:
// +usage=The alias is the email alias to show after sending the email
alias?: string
// +usage=Specify the password of the email, you can either sepcify it in value or use secretRef
password: value | secretRef
password: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +usage=Specify the host of your email
host: string
// +usage=Specify the port of the email host, default to 587
@@ -150,13 +177,6 @@ spec:
description?: textType
url?: string
}
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
value: string
// send webhook notification
ding: op.#Steps & {
if parameter.dingding != _|_ {

View File

@@ -68,14 +68,18 @@ spec:
} @step(8)
}
}
secretRef: {
name: string
key: string
}
value: string
parameter: {
// +usage=Specify the webhook url
url: value | secretRef
url: {
value: string
} | {
secretRef: {
// +usage=name is the name of the secret
name: string
// +usage=key is the key in the secret
key: string
}
}
// +usage=Specify the data you want to send
data?: {...}
}

View File

@@ -494,7 +494,12 @@ spec:
replica: strconv.FormatInt(context.output.status.readyReplicas, 10)
}
}
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
if context.output.status.replicas != _|_ {
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
}
if context.output.status.replicas == _|_ {
message: ""
}
healthPolicy: |-
ready: {
if context.output.status.readyReplicas == _|_ {
@@ -504,7 +509,12 @@ spec:
replica: context.output.status.readyReplicas
}
}
isHealth: context.output.status.replicas == context.output.status.readyReplicas
if context.output.status.replicas != _|_ {
isHealth: context.output.status.replicas == ready.replica
}
if context.output.status.replicas == _|_ {
isHealth: false
}
workload:
definition:
apiVersion: apps/v1

View File

@@ -405,7 +405,12 @@ spec:
replica: strconv.FormatInt(context.output.status.readyReplicas, 10)
}
}
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
if context.output.status.replicas != _|_ {
message: "Ready:" + ready.replica + "/" + strconv.FormatInt(context.output.status.replicas, 10)
}
if context.output.status.replicas == _|_ {
message: ""
}
healthPolicy: |-
ready: {
if context.output.status.readyReplicas == _|_ {
@@ -415,7 +420,12 @@ spec:
replica: context.output.status.readyReplicas
}
}
isHealth: context.output.status.replicas == context.output.status.readyReplicas
if context.output.status.replicas != _|_ {
isHealth: context.output.status.replicas == ready.replica
}
if context.output.status.replicas == _|_ {
isHealth: false
}
workload:
definition:
apiVersion: apps/v1

View File

@@ -135,27 +135,52 @@ Start to test.
make e2e-test
```
## Contribute apiserver and [velaux](https://github.com/oam-dev/velaux)
Before start, please make sure you have already started the vela controller environment.
```shell
make run-apiserver
```
By default, the apiserver will serving at "0.0.0.0:8000".
Get the velaux code by:
```shell
git clone git@github.com:oam-dev/velaux.git
```
Configure the apiserver address:
```shell
cd velaux
echo "BASE_DOMAIN='http://127.0.0.1:8000'" > .env
```
Make sure you have installed [yarn](https://classic.yarnpkg.com/en/docs/install).
```shell
yarn install
yarn start
```
To execute the e2e test of the API module, the mongodb service needs to exist locally.
```shell script
# save your config
mv ~/.kube/config ~/.kube/config.save
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
kind get kubeconfig --name worker > /tmp/worker.client.kubeconfig
# restore your config
mv ~/.kube/config.save ~/.kube/config
make e2e-apiserver-test
```
## Contribute Docs
Please read [the documentation](https://github.com/oam-dev/kubevela/tree/master/docs/README.md) before contributing to the docs.
- Build docs
```shell script
make docs-build
```
- Local development and preview
```shell script
make docs-start
```
## Next steps

File diff suppressed because it is too large Load Diff

View File

@@ -10,46 +10,69 @@ spec:
import ("vela/op")
parameter: {
repoUrl: string
dockerfile: string
image: string
// +usage=Specify the SSH URL of git repository for your source codes, e.g. git@git.woa.com:my-git-repo/demo.git
repoUrl: string
// +usage=Specify the branch which you want to be checked out and built
branch: string
// +usage=Specify the filename of Dockerfile under the root folder in your git repository
dockerfile: string
// +usage=Specify the secret name for the docker repository in your namespace in your K8S cluster
dockerSecret: string
// +usage=Specify the address where the image built is pushed into
image: string
}
let pushImage=image
build: op.#Task & {
name: "\(context.name)-\(context.stepId)"
namespace: context.namespace
name: "\(context.name)-build-\(context.stepSessionID)"
namespace: context.namespace
// Declare workspaces that used to share data.
workspace: {
"code-dir": {}
}
toolImage: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/entrypoint:v0.27.2"
baseImage: "busybox:1.34"
steps: [
// git clone code repo.
{
name: "git-clone"
image: "git-image-with-pull-ssh"
workspaceMounts: [{workspace: workspace["code-dir"], mountPath: "/data/code"}]
script: """
#! /bin/bash
git clone \(repourl) /data/code
"""
},
// build and push image.
{
name: "build-image"
image: "gcr.io/kaniko-project/executor:v1.3.0"
workspaceMounts: [{workspace: workspace["code-dir"], mountPath: "/data/build"}]
script: """
#! /bin/bash
/kaniko/executor --dockerfile=/data/build/\(dockerfile) --context=/data/build --destination=\(pushImage)
"""
}]
// Declare workspaces that used to share data.
workspaces: {
"code-dir": {}
}
secrets: {
"\(parameter.dockerSecret)": {
items: [{
key: ".dockerconfigjson"
path: "config.json"
}]
}
}
steps: [
// git clone code repo.
{
name: "git-clone"
image: "bitnami/git:2.34.1"
workspaceMounts: [{workspace: workspaces["code-dir"], mountPath: "/data/code"}]
script: """
#!/bin/bash
git clone -b \(parameter.branch) \(parameter.repoUrl) /data/code
"""
},
// build and push image.
{
name: "build-image"
image: "gcr.io/kaniko-project/executor:debug"
workspaceMounts: [{workspace: workspaces["code-dir"], mountPath: "/data/build"}]
secretMounts: [{secret: secrets["\(parameter.dockerSecret)"], mountPath: "/kaniko/.docker/"}]
script: """
#!/busybox/sh
/kaniko/executor --dockerfile=/data/build/\(parameter.dockerfile) --context=/data/build --destination=\(parameter.image)
"""
}]
}
// wait until deployment ready
wait: op.#ConditionalWait & {
continue: build.do.status.phase == "successed"
continue: build.apply.value.status.phase == "Succeeded"
}

View File

@@ -50,6 +50,12 @@ var _ = Describe("Addon Test", func() {
Expect(output).To(ContainSubstring("Successfully enable addon"))
})
It("Upgrade addon test-addon", func() {
output, err := e2e.Exec("vela addon upgrade test-addon")
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring("Successfully enable addon"))
})
It("Disable addon test-addon", func() {
output, err := e2e.LongTimeExec("vela addon disable test-addon", 600*time.Second)
Expect(err).NotTo(HaveOccurred())
@@ -90,12 +96,12 @@ var _ = Describe("Addon Test", func() {
})
It("Add test addon registry", func() {
output, err := e2e.LongTimeExec("vela addon registry add my-repo --type=git --gitUrl=https://github.com/oam-dev/catalog --path=/experimental/addons", 600*time.Second)
output, err := e2e.LongTimeExec("vela addon registry add my-repo --type=git --endpoint=https://github.com/oam-dev/catalog --path=/experimental/addons", 600*time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(output).To(ContainSubstring("Successfully add an addon registry my-repo"))
Eventually(func() error {
output, err := e2e.LongTimeExec("vela addon registry update my-repo --type=git --gitUrl=https://github.com/oam-dev/catalog --path=/addons", 300*time.Second)
output, err := e2e.LongTimeExec("vela addon registry update my-repo --type=git --endpoint=https://github.com/oam-dev/catalog --path=/addons", 300*time.Second)
if err != nil {
return err
}

View File

@@ -46,9 +46,9 @@ var (
var _ = ginkgo.Describe("Test Vela Application", func() {
e2e.JsonAppFileContext("json appfile apply", jsonAppFile)
e2e.EnvSetContext("env set", "default")
e2e.EnvSetContext("env set default", "default")
e2e.DeleteEnvFunc("env delete", envName)
e2e.EnvInitContext("env init", envName)
e2e.EnvInitContext("env init env-application", envName)
e2e.EnvSetContext("env set", envName)
e2e.JsonAppFileContext("deploy app-basic", appbasicJsonAppFile)
ApplicationExecContext("exec -- COMMAND", applicationName)
@@ -185,7 +185,7 @@ var ApplicationDeleteWithWaitOptions = func(context string, appName string) bool
cli := fmt.Sprintf("vela delete %s --wait", appName)
output, err := e2e.ExecAndTerminate(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
gomega.Expect(output).To(gomega.ContainSubstring("deleted"))
})
})
}
@@ -225,7 +225,7 @@ var ApplicationDeleteWithForceOptions = func(context string, appName string) boo
cli = fmt.Sprintf("vela delete %s --force", appName)
output, err = e2e.ExecAndTerminate(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
gomega.Expect(output).To(gomega.ContainSubstring("deleted"))
})
})
}

View File

@@ -2,11 +2,9 @@ createTime: "0001-01-01T00:00:00Z"
name: initmyapp
services:
mysvc:
addRevisionLabel: false
cpu: "0.5"
image: nginx:latest
imagePullPolicy: Always
memory: 200M
port: 80
type: webservice
updateTime: "0001-01-01T00:00:00Z"

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"os"
"github.com/Netflix/go-expect"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
@@ -31,9 +32,29 @@ var (
return ginkgo.Context(context, func() {
ginkgo.It("should print environment initiation successful message", func() {
cli := fmt.Sprintf("vela env init %s", envName)
output, err := Exec(cli)
var answer = "default"
if envName != "env-application" {
answer = "vela-system"
}
output, err := InteractiveExec(cli, func(c *expect.Console) {
data := []struct {
q, a string
}{
{
q: "Would you like to choose an existing namespaces as your env?",
a: answer,
},
}
for _, qa := range data {
_, err := c.ExpectString(qa.q)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
_, err = c.SendLine(qa.a)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
}
c.ExpectEOF()
})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("environment %s created", envName)
expectedOutput := fmt.Sprintf("environment %s with namespace %s created", envName, answer)
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
})
})
@@ -45,7 +66,7 @@ var (
cli := fmt.Sprintf("vela env init %s --namespace %s", envName, namespace)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("environment %s created", envName)
expectedOutput := fmt.Sprintf("environment %s with namespace %s created", envName, namespace)
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
})
})
@@ -92,8 +113,7 @@ var (
cli := fmt.Sprintf("vela env sw %s", envName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("Set environment succeed, current environment is %s", envName)
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
gomega.Expect(output).To(gomega.ContainSubstring(envName))
})
})
}
@@ -109,17 +129,6 @@ var (
})
})
}
EnvDeleteCurrentUsingContext = func(context string, envName string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should delete all envs", func() {
cli := fmt.Sprintf("vela env delete %s", envName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("Error: you can't delete current using environment %s", envName)
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
})
})
}
WorkloadDeleteContext = func(context string, applicationName string) bool {
return ginkgo.Context(context, func() {

5
e2e/env/env_test.go vendored
View File

@@ -29,8 +29,8 @@ var (
)
var _ = ginkgo.Describe("Env", func() {
e2e.EnvInitContext("env init", envName)
e2e.EnvInitContext("env init another one", envName2)
e2e.EnvInitWithNamespaceOptionContext("env init env-hello --namespace heelo", envName, "heelo")
e2e.EnvInitWithNamespaceOptionContext("env init another one --namespace heelo2", envName2, "heelo2")
e2e.EnvShowContext("env show", envName)
e2e.EnvSetContext("env set", envName)
@@ -46,5 +46,4 @@ var _ = ginkgo.Describe("Env", func() {
})
e2e.EnvDeleteContext("env delete", envName2)
e2e.EnvDeleteCurrentUsingContext("env delete currently using one", envName)
})

26
makefiles/build.mk Normal file
View File

@@ -0,0 +1,26 @@
.PHONY: vela-cli
vela-cli:
$(GOBUILD_ENV) go build -o bin/vela -a -ldflags $(LDFLAGS) ./references/cmd/cli/main.go
.PHONY: kubectl-vela
kubectl-vela:
$(GOBUILD_ENV) go build -o bin/kubectl-vela -a -ldflags $(LDFLAGS) ./cmd/plugin/main.go
# Build the docker image
.PHONY: docker-build
docker-build: docker-build-core docker-build-apiserver
@$(OK)
.PHONY: docker-build-core
docker-build-core:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_CORE_IMAGE) .
.PHONY: docker-build-apiserver
docker-build-apiserver:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_APISERVER_IMAGE) -f Dockerfile.apiserver .
# Build the runtime docker image
.PHONY: docker-build-runtime-rollout
docker-build-runtime-rollout:
docker build --build-arg=VERSION=$(VELA_VERSION) --build-arg=GITVERSION=$(GIT_COMMIT) -t $(VELA_RUNTIME_ROLLOUT_IMAGE) -f runtime/rollout/Dockerfile .

51
makefiles/const.mk Normal file
View File

@@ -0,0 +1,51 @@
SHELL := /bin/bash
GOBUILD_ENV = GO111MODULE=on CGO_ENABLED=0
GOX = go run github.com/mitchellh/gox
TARGETS := darwin/amd64 linux/amd64 windows/amd64
DIST_DIRS := find * -type d -exec
TIME_LONG = `date +%Y-%m-%d' '%H:%M:%S`
TIME_SHORT = `date +%H:%M:%S`
TIME = $(TIME_SHORT)
BLUE := $(shell printf "\033[34m")
YELLOW := $(shell printf "\033[33m")
RED := $(shell printf "\033[31m")
GREEN := $(shell printf "\033[32m")
CNone := $(shell printf "\033[0m")
INFO = echo ${TIME} ${BLUE}[ .. ]${CNone}
WARN = echo ${TIME} ${YELLOW}[WARN]${CNone}
ERR = echo ${TIME} ${RED}[FAIL]${CNone}
OK = echo ${TIME} ${GREEN}[ OK ]${CNone}
FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)
# Vela version
VELA_VERSION ?= master
# Repo info
GIT_COMMIT ?= git-$(shell git rev-parse --short HEAD)
GIT_COMMIT_LONG ?= $(shell git rev-parse HEAD)
VELA_VERSION_KEY := github.com/oam-dev/kubevela/version.VelaVersion
VELA_GITVERSION_KEY := github.com/oam-dev/kubevela/version.GitRevision
LDFLAGS ?= "-s -w -X $(VELA_VERSION_KEY)=$(VELA_VERSION) -X $(VELA_GITVERSION_KEY)=$(GIT_COMMIT)"
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# Image URL to use all building/pushing image targets
VELA_CORE_IMAGE ?= vela-core:latest
VELA_CORE_TEST_IMAGE ?= vela-core-test:$(GIT_COMMIT)
VELA_APISERVER_IMAGE ?= apiserver:latest
VELA_RUNTIME_ROLLOUT_IMAGE ?= vela-runtime-rollout:latest
VELA_RUNTIME_ROLLOUT_TEST_IMAGE ?= vela-runtime-rollout-test:$(GIT_COMMIT)
RUNTIME_CLUSTER_CONFIG ?= /tmp/worker.kubeconfig
RUNTIME_CLUSTER_NAME ?= worker

73
makefiles/dependency.mk Normal file
View File

@@ -0,0 +1,73 @@
GOLANGCILINT_VERSION ?= v1.38.0
.PHONY: golangci
golangci:
ifneq ($(shell which golangci-lint),)
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(shell which golangci-lint)
else ifeq (, $(shell which $(GOBIN)/golangci-lint))
@{ \
set -e ;\
echo 'installing golangci-lint-$(GOLANGCILINT_VERSION)' ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) $(GOLANGCILINT_VERSION) ;\
echo 'Successfully installed' ;\
}
GOLANGCILINT=$(GOBIN)/golangci-lint
else
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(GOBIN)/golangci-lint
endif
.PHONY: staticchecktool
staticchecktool:
ifeq (, $(shell which staticcheck))
@{ \
set -e ;\
echo 'installing honnef.co/go/tools/cmd/staticcheck ' ;\
GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck ;\
}
STATICCHECK=$(GOBIN)/staticcheck
else
STATICCHECK=$(shell which staticcheck)
endif
.PHONY: goimports
goimports:
ifeq (, $(shell which goimports))
@{ \
set -e ;\
GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports ;\
}
GOIMPORTS=$(GOBIN)/goimports
else
GOIMPORTS=$(shell which goimports)
endif
.PHONY: installcue
installcue:
ifeq (, $(shell which cue))
@{ \
set -e ;\
GO111MODULE=off go get -u cuelang.org/go/cmd/cue ;\
}
CUE=$(GOBIN)/cue
else
CUE=$(shell which cue)
endif
KUSTOMIZE_VERSION ?= 3.8.2
.PHONY: kustomize
kustomize:
ifeq (, $(shell kustomize version | grep $(KUSTOMIZE_VERSION)))
@{ \
set -eo pipefail ;\
echo 'installing kustomize-v$(KUSTOMIZE_VERSION) into $(GOBIN)' ;\
curl -sS https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash -s $(KUSTOMIZE_VERSION) $(GOBIN);\
echo 'Install succeed' ;\
}
KUSTOMIZE=$(GOBIN)/kustomize
else
KUSTOMIZE=$(shell which kustomize)
endif

32
makefiles/develop.mk Normal file
View File

@@ -0,0 +1,32 @@
# Run apiserver for velaux(UI)
.PHONY: run-apiserver
run-apiserver:
go run ./cmd/apiserver/main.go
# Install CRDs and Definitions of Vela Core into a cluster, this is for develop convenient.
.PHONY: core-install
core-install: manifests
kubectl apply -f hack/namespace.yaml
kubectl apply -f charts/vela-core/crds/
@$(OK) install succeed
# Uninstall CRDs and Definitions of Vela Core from a cluster, this is for develop convenient.
.PHONY: core-uninstall
core-uninstall: manifests
kubectl delete -f charts/vela-core/crds/
# Run against the configured Kubernetes cluster in ~/.kube/config
.PHONY: run
run:
go run ./cmd/core/main.go --application-revision-limit 5
# Run against the configured Kubernetes cluster in ~/.kube/config with debug logs
.PHONY: core-debug-run
core-debug-run: fmt vet manifests
go run ./cmd/core/main.go --log-debug=true
# Run against the configured Kubernetes cluster in ~/.kube/config
.PHONY: core-run
core-run: fmt vet manifests
go run ./cmd/core/main.go

80
makefiles/e2e.mk Normal file
View File

@@ -0,0 +1,80 @@
.PHONY: e2e-setup-core
e2e-setup-core:
sh ./hack/e2e/modify_charts.sh
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set applicationRevisionLimit=5 --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
kubectl wait --for=condition=Available deployment/kubevela-vela-core -n vela-system --timeout=180s
go run ./e2e/addon/mock &
.PHONY: setup-runtime-e2e-cluster
setup-runtime-e2e-cluster:
helm upgrade --install --create-namespace --namespace vela-system --kubeconfig=$(RUNTIME_CLUSTER_CONFIG) --set image.pullPolicy=IfNotPresent --set image.repository=vela-runtime-rollout-test --set image.tag=$(GIT_COMMIT) --wait vela-rollout ./runtime/rollout/charts
.PHONY: e2e-setup
e2e-setup:
helm install kruise https://github.com/openkruise/kruise/releases/download/v0.9.0/kruise-chart.tgz --set featureGates="PreDownloadImageForInPlaceUpdate=true"
sh ./hack/e2e/modify_charts.sh
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set applicationRevisionLimit=5 --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
helm upgrade --install --create-namespace --namespace oam-runtime-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set dependCheckWait=10s --set image.tag=$(GIT_COMMIT) --wait oam-runtime ./charts/oam-runtime
go run ./e2e/addon/mock &
bin/vela addon enable fluxcd
bin/vela addon enable terraform
bin/vela addon enable terraform-alibaba ALICLOUD_ACCESS_KEY=xxx ALICLOUD_SECRET_KEY=yyy ALICLOUD_REGION=cn-beijing
ginkgo version
ginkgo -v -r e2e/setup
timeout 600s bash -c -- 'while true; do kubectl get ns flux-system; if [ $$? -eq 0 ] ; then break; else sleep 5; fi;done'
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
kubectl wait --for=condition=Ready pod -l app=source-controller -n flux-system --timeout=600s
kubectl wait --for=condition=Ready pod -l app=helm-controller -n flux-system --timeout=600s
.PHONY: e2e-api-test
e2e-api-test:
# Run e2e test
ginkgo -v -skipPackage capability,setup,application -r e2e
ginkgo -v -r e2e/application
ADDONSERVER = $(shell pgrep vela_addon_mock_server)
.PHONY: e2e-apiserver-test
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
@$(OK) tests pass
.PHONY: e2e-test
e2e-test:
# Run e2e test
ginkgo -v --skip="rollout related e2e-test." ./test/e2e-test
@$(OK) tests pass
.PHONY: e2e-addon-test
e2e-addon-test:
cp bin/vela /tmp/
ginkgo -v ./test/e2e-addon-test
@$(OK) tests pass
.PHONY: e2e-rollout-test
e2e-rollout-test:
ginkgo -v --focus="rollout related e2e-test." ./test/e2e-test
@$(OK) tests pass
.PHONY: e2e-multicluster-test
e2e-multicluster-test:
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_multicluster_test.out ./test/e2e-multicluster-test
@$(OK) tests pass
.PHONY: e2e-cleanup
e2e-cleanup:
# Clean up
rm -rf ~/.vela
.PHONY: end-e2e-core
end-e2e-core:
sh ./hack/e2e/end_e2e_core.sh
.PHONY: end-e2e
end-e2e:
sh ./hack/e2e/end_e2e.sh

28
makefiles/release.mk Normal file
View File

@@ -0,0 +1,28 @@
.PHONY: cross-build
cross-build:
rm -rf _bin
go get github.com/mitchellh/gox@v0.4.0
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/vela/{{.OS}}-{{.Arch}}/vela" -osarch='$(TARGETS)' ./references/cmd/cli
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/kubectl-vela/{{.OS}}-{{.Arch}}/kubectl-vela" -osarch='$(TARGETS)' ./cmd/plugin
$(GOBUILD_ENV) $(GOX) -ldflags $(LDFLAGS) -parallel=2 -output="_bin/apiserver/{{.OS}}-{{.Arch}}/apiserver" -osarch="$(TARGETS)" ./cmd/apiserver
.PHONY: compress
compress:
( \
echo "\n## Release Info\nVERSION: $(VELA_VERSION)" >> README.md && \
echo "GIT_COMMIT: $(GIT_COMMIT_LONG)\n" >> README.md && \
cd _bin/vela && \
$(DIST_DIRS) cp ../../LICENSE {} \; && \
$(DIST_DIRS) cp ../../README.md {} \; && \
$(DIST_DIRS) tar -zcf vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r vela-{}.zip {} \; && \
cd ../kubectl-vela && \
$(DIST_DIRS) cp ../../LICENSE {} \; && \
$(DIST_DIRS) cp ../../README.md {} \; && \
$(DIST_DIRS) tar -zcf kubectl-vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r kubectl-vela-{}.zip {} \; && \
cd .. && \
sha256sum vela/vela-* kubectl-vela/kubectl-vela-* > sha256sums.txt \
)

View File

@@ -102,10 +102,21 @@ var (
CLIMetaOptions = ListOptions{}
)
const (
// ObservabilityAddon is the name of the observability addon
ObservabilityAddon = "observability"
// ObservabilityAddonEndpointComponent is the endpoint component name of the observability addon
ObservabilityAddonEndpointComponent = "grafana"
// ObservabilityAddonDomainArg is the domain argument name of the observability addon
ObservabilityAddonDomainArg = "domain"
)
// ObservabilityEnvironment contains the Observability addon's domain for each cluster
type ObservabilityEnvironment struct {
Cluster string
Domain string
Cluster string
Domain string
LoadBalancerIP string
ServiceExternalIP string
}
// ObservabilityEnvBindingValues is a list of ObservabilityEnvironment and will be used to render observability-env-binding.yaml
@@ -125,14 +136,6 @@ const (
placement:
clusterSelector:
name: {{.Cluster}}
patch:
components:
- name: grafana
type: helm
traits:
- type: pure-ingress
properties:
domain: {{.Domain}}
{{ end }}
{{ end }}`
@@ -178,7 +181,7 @@ func GetPatternFromItem(it Item, r AsyncReader, rootPath string) string {
}
// ListAddonUIDataFromReader list addons from AsyncReader
func ListAddonUIDataFromReader(r AsyncReader, registryMeta map[string]SourceMeta, opt ListOptions) ([]*UIData, error) {
func ListAddonUIDataFromReader(r AsyncReader, registryMeta map[string]SourceMeta, registryName string, opt ListOptions) ([]*UIData, error) {
var addons []*UIData
var err error
var wg sync.WaitGroup
@@ -196,6 +199,7 @@ func ListAddonUIDataFromReader(r AsyncReader, registryMeta map[string]SourceMeta
errCh <- err
return
}
addonRes.RegistryName = registryName
l.Lock()
addons = append(addons, addonRes)
l.Unlock()
@@ -480,7 +484,7 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
if err != nil {
return nil, ErrRenderCueTmpl
}
if addon.Name == "observability" && strings.HasSuffix(comp.Name, ".cue") {
if addon.Name == ObservabilityAddon && strings.HasSuffix(comp.Name, ".cue") {
comp.Name = strings.Split(comp.Name, ".cue")[0]
}
app.Spec.Components = append(app.Spec.Components, *comp)
@@ -500,13 +504,8 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
Name: "deploy-runtime",
Type: "deploy2runtime",
})
case addon.Name == "observability":
arg, ok := args["domain"]
if !ok {
return nil, ErrorNoDomain
}
domain := arg.(string)
policies, err := preparePolicies4Observability(ctx, k8sClient, domain)
case addon.Name == ObservabilityAddon:
policies, err := preparePolicies4Observability(ctx, k8sClient)
if err != nil {
return nil, errors.Wrap(err, "fail to render the policies for Add-on Observability")
}
@@ -519,7 +518,7 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
}},
}
workflowSteps, err := prepareWorkflow4Observability(ctx, k8sClient, domain)
workflowSteps, err := prepareWorkflow4Observability(ctx, k8sClient)
if err != nil {
return nil, errors.Wrap(err, "fail to prepare the workflow for Add-on Observability")
}
@@ -607,7 +606,7 @@ func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured
return schemaConfigmaps, nil
}
func allocateDomainForAddon(ctx context.Context, k8sClient client.Client, domain string) ([]ObservabilityEnvironment, error) {
func allocateDomainForAddon(ctx context.Context, k8sClient client.Client) ([]ObservabilityEnvironment, error) {
secrets, err := multicluster.ListExistingClusterSecrets(ctx, k8sClient)
if err != nil {
klog.Error(err, "failed to list existing cluster secrets")
@@ -618,18 +617,16 @@ func allocateDomainForAddon(ctx context.Context, k8sClient client.Client, domain
for i, secret := range secrets {
cluster := secret.Name
domain := fmt.Sprintf("%s.%s", cluster, domain)
envs[i] = ObservabilityEnvironment{
Cluster: cluster,
Domain: domain,
}
}
return envs, nil
}
func preparePolicies4Observability(ctx context.Context, k8sClient client.Client, domain string) ([]v1beta1.AppPolicy, error) {
clusters, err := allocateDomainForAddon(ctx, k8sClient, domain)
func preparePolicies4Observability(ctx context.Context, k8sClient client.Client) ([]v1beta1.AppPolicy, error) {
clusters, err := allocateDomainForAddon(ctx, k8sClient)
if err != nil {
return nil, err
}
@@ -659,8 +656,8 @@ func preparePolicies4Observability(ctx context.Context, k8sClient client.Client,
return policies, nil
}
func prepareWorkflow4Observability(ctx context.Context, k8sClient client.Client, domain string) ([]v1beta1.WorkflowStep, error) {
clusters, err := allocateDomainForAddon(ctx, k8sClient, domain)
func prepareWorkflow4Observability(ctx context.Context, k8sClient client.Client) ([]v1beta1.WorkflowStep, error) {
clusters, err := allocateDomainForAddon(ctx, k8sClient)
if err != nil {
return nil, err
}
@@ -882,7 +879,7 @@ func (h *Installer) loadInstallPackage(name string) (*InstallPackage, error) {
meta, ok := metas[name]
if !ok {
return nil, errors.Wrapf(err, "fail to find the addon meta of %s", name)
return nil, ErrNotExist
}
var uiData *UIData
uiData, err = h.cache.GetUIData(*h.r, name)

View File

@@ -34,12 +34,14 @@ import (
corev1 "k8s.io/api/core/v1"
"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"
"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"
)
var paths = []string{
@@ -120,7 +122,7 @@ func testReaderFunc(t *testing.T, reader AsyncReader) {
assert.True(t, len(uiData.Definitions) > 0)
// test get ui data
uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, UIMetaOptions)
uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, "KubeVela", UIMetaOptions)
assert.True(t, strings.Contains(err.Error(), "#parameter.example: preference mark not allowed at this position"))
assert.Equal(t, len(uiDataList), 3)
@@ -151,11 +153,9 @@ func TestRender(t *testing.T) {
envs: []ObservabilityEnvironment{
{
Cluster: "c1",
Domain: "a.com",
},
{
Cluster: "c2",
Domain: "b.com",
},
},
tmpl: ObservabilityEnvBindingEnvTmpl,
@@ -166,27 +166,11 @@ func TestRender(t *testing.T) {
placement:
clusterSelector:
name: c1
patch:
components:
- name: grafana
type: helm
traits:
- type: pure-ingress
properties:
domain: a.com
- name: c2
placement:
clusterSelector:
name: c2
patch:
components:
- name: grafana
type: helm
traits:
- type: pure-ingress
properties:
domain: b.com
`,
@@ -196,11 +180,9 @@ func TestRender(t *testing.T) {
envs: []ObservabilityEnvironment{
{
Cluster: "c1",
Domain: "a.com",
},
{
Cluster: "c2",
Domain: "b.com",
},
},
tmpl: ObservabilityWorkflow4EnvBindingTmpl,
@@ -324,6 +306,76 @@ func TestGetAddonStatus(t *testing.T) {
}
}
func TestGetAddonStatus4Observability(t *testing.T) {
ctx := context.Background()
addonApplication := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "observability",
Namespace: types.DefaultKubeVelaNS,
},
Status: common.AppStatus{
Phase: common.ApplicationRunning,
},
}
addonSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: Convert2SecName(ObservabilityAddon),
Namespace: types.DefaultKubeVelaNS,
},
Data: map[string][]byte{},
}
addonService := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: types.DefaultKubeVelaNS,
Name: ObservabilityAddonEndpointComponent,
},
Status: corev1.ServiceStatus{
LoadBalancer: corev1.LoadBalancerStatus{
Ingress: []corev1.LoadBalancerIngress{
{
IP: "1.2.3.4",
},
},
},
},
}
clusterSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Labels: map[string]string{
v1alpha12.LabelKeyClusterCredentialType: string(v1alpha12.CredentialTypeX509Certificate),
},
},
Data: map[string][]byte{
"test-key": []byte("test-value"),
},
}
scheme := runtime.NewScheme()
assert.NoError(t, v1beta1.AddToScheme(scheme))
assert.NoError(t, corev1.AddToScheme(scheme))
k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(addonApplication, addonSecret).Build()
addonStatus, err := GetAddonStatus(context.Background(), k8sClient, ObservabilityAddon)
assert.NoError(t, err)
assert.Equal(t, addonStatus.AddonPhase, enabling)
// Addon is not installed in multiple clusters
k8sClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(addonApplication, addonSecret, addonService).Build()
addonStatus, err = GetAddonStatus(context.Background(), k8sClient, ObservabilityAddon)
assert.NoError(t, err)
assert.Equal(t, addonStatus.AddonPhase, enabled)
// Addon is installed in multiple clusters
assert.NoError(t, k8sClient.Create(ctx, clusterSecret))
addonStatus, err = GetAddonStatus(context.Background(), k8sClient, ObservabilityAddon)
assert.NoError(t, err)
assert.Equal(t, addonStatus.AddonPhase, enabled)
}
var baseAddon = InstallPackage{
Meta: Meta{
Name: "test-render-cue-definition-addon",
@@ -383,18 +435,6 @@ func TestRenderApp4Observability(t *testing.T) {
},
},
args: map[string]interface{}{},
application: "",
err: ErrorNoDomain,
},
{
addon: InstallPackage{
Meta: Meta{
Name: "observability",
},
},
args: map[string]interface{}{
"domain": "a.com",
},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"}]}},"status":{}}`,
},
}
@@ -441,10 +481,8 @@ func TestRenderApp4ObservabilityWithK8sData(t *testing.T) {
Name: "observability",
},
},
args: map[string]interface{}{
"domain": "a.com",
},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","patch":{"components":[{"name":"grafana","traits":[{"properties":{"domain":"test-secret.a.com"},"type":"pure-ingress"}],"type":"helm"}]},"placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
args: map[string]interface{}{},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
},
}
for _, tc := range testcases {

View File

@@ -18,14 +18,20 @@ package addon
import (
"context"
"encoding/json"
"fmt"
"k8s.io/klog/v2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -78,11 +84,46 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
}
return Status{}, err
}
if app.Status.Workflow != nil && app.Status.Workflow.Suspend {
return Status{AddonPhase: suspend, AppStatus: &app.Status}, nil
}
switch app.Status.Phase {
case commontypes.ApplicationRunning:
if name == ObservabilityAddon {
var (
clusters = make(map[string]map[string]interface{})
sec v1.Secret
domain string
)
if err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: Convert2SecName(name)}, &sec); err != nil {
klog.ErrorS(err, "failed to get observability secret")
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
}
if v, ok := sec.Data[ObservabilityAddonDomainArg]; ok {
domain = string(v)
}
observability, err := GetObservabilityAccessibilityInfo(ctx, cli, domain)
if err != nil {
klog.ErrorS(err, "failed to get observability accessibility info")
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
}
for _, o := range observability {
var access = fmt.Sprintf("No loadBalancer found, visiting by using 'vela port-forward %s", ObservabilityAddon)
if o.LoadBalancerIP != "" {
access = fmt.Sprintf("Visiting URL: %s, IP: %s", o.Domain, o.LoadBalancerIP)
}
clusters[o.Cluster] = map[string]interface{}{
"domain": o.Domain,
"loadBalancerIP": o.LoadBalancerIP,
"access": access,
"serviceExternalIP": o.ServiceExternalIP,
}
}
return Status{AddonPhase: enabled, AppStatus: &app.Status, Clusters: clusters}, nil
}
return Status{AddonPhase: enabled, AppStatus: &app.Status}, nil
case commontypes.ApplicationDeleting:
return Status{AddonPhase: disabling, AppStatus: &app.Status}, nil
@@ -91,8 +132,60 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
}
}
// GetObservabilityAccessibilityInfo will get the accessibility info of addon in local cluster and multiple clusters
func GetObservabilityAccessibilityInfo(ctx context.Context, k8sClient client.Client, domain string) ([]ObservabilityEnvironment, error) {
domains, err := allocateDomainForAddon(ctx, k8sClient)
if err != nil {
return nil, err
}
obj := new(unstructured.Unstructured)
obj.SetKind("Service")
obj.SetAPIVersion("v1")
key := client.ObjectKeyFromObject(obj)
key.Namespace = types.DefaultKubeVelaNS
key.Name = ObservabilityAddonEndpointComponent
for i, d := range domains {
if err != nil {
return nil, err
}
readCtx := multicluster.ContextWithClusterName(ctx, d.Cluster)
if err := k8sClient.Get(readCtx, key, obj); err != nil {
return nil, err
}
var svc v1.Service
data, err := obj.MarshalJSON()
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &svc); err != nil {
return nil, err
}
if svc.Status.LoadBalancer.Ingress != nil && len(svc.Status.LoadBalancer.Ingress) == 1 {
domains[i].ServiceExternalIP = svc.Status.LoadBalancer.Ingress[0].IP
}
}
// set domain for the cluster if there is no child clusters
if len(domains) == 0 {
var svc v1.Service
if err := k8sClient.Get(ctx, client.ObjectKey{Name: ObservabilityAddonEndpointComponent, Namespace: types.DefaultKubeVelaNS}, &svc); err != nil {
return nil, err
}
if svc.Status.LoadBalancer.Ingress != nil && len(svc.Status.LoadBalancer.Ingress) == 1 {
domains = []ObservabilityEnvironment{
{
ServiceExternalIP: svc.Status.LoadBalancer.Ingress[0].IP,
},
}
}
}
return domains, nil
}
// Status contain addon phase and related app status
type Status struct {
AddonPhase string
AppStatus *commontypes.AppStatus
// the status of multiple clusters
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
}

View File

@@ -191,7 +191,7 @@ func (r *Registry) ListUIData(registryAddonMeta map[string]SourceMeta, opt ListO
if err != nil {
return nil, err
}
return ListAddonUIDataFromReader(reader, registryAddonMeta, opt)
return ListAddonUIDataFromReader(reader, registryAddonMeta, r.Name, opt)
}
// GetInstallPackage get install package which is all needed to enable an addon from addon registry

View File

@@ -36,6 +36,7 @@ type UIData struct {
Definitions []ElementFile `json:"definitions"`
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
Parameters string `json:"parameters"`
RegistryName string `json:"registryName"`
}
// InstallPackage contains all necessary files that can be installed for an addon

View File

@@ -123,13 +123,13 @@ type ListOptions struct {
// DataStore datastore interface
type DataStore interface {
// add entity to database, Name() and TableName() can't return zero value.
// Add adds entity to database, Name() and TableName() can't return zero value.
Add(ctx context.Context, entity Entity) error
// batch add entity to database, Name() and TableName() can't return zero value.
BatchAdd(ctx context.Context, entitys []Entity) error
// BatchAdd will adds batched entities to database, Name() and TableName() can't return zero value.
BatchAdd(ctx context.Context, entities []Entity) error
// Update entity to database, Name() and TableName() can't return zero value.
// Put will update entity to database, Name() and TableName() can't return zero value.
Put(ctx context.Context, entity Entity) error
// Delete entity from database, Name() and TableName() can't return zero value.

View File

@@ -59,7 +59,7 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
if err := kubeClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: cfg.Database,
Annotations: map[string]string{"description": "For kubevela apiserver metadata storage."},
Annotations: map[string]string{"description": "For KubeVela API Server metadata storage."},
}}); err != nil {
return nil, fmt.Errorf("create namespace failure %w", err)
}
@@ -247,10 +247,15 @@ func newBySortOptionConfigMap(items []corev1.ConfigMap, sortBy []datastore.SortO
data := item.BinaryData["data"]
for _, op := range sortBy {
res := gjson.Get(string(data), op.Key)
if res.Type == gjson.Number {
switch res.Type {
case gjson.Number:
m[op.Key] = res.Num
} else {
m[op.Key] = res.Raw
default:
if !res.Time().IsZero() {
m[op.Key] = res.Time()
} else {
m[op.Key] = res.Raw
}
}
}
s.objects[i] = m
@@ -271,25 +276,28 @@ func (b bySortOptionConfigMap) Less(i, j int) bool {
for _, op := range b.sortBy {
x := b.objects[i][op.Key]
y := b.objects[j][op.Key]
_x, xok := x.(float64)
_y, yok := y.(float64)
var lt, gt bool
_x, xok := x.(time.Time)
_y, yok := y.(time.Time)
var xScore, yScore float64
if xok && yok {
lt, gt = _x < _y, _x > _y
xScore = float64(_x.UnixNano())
yScore = float64(_y.UnixNano())
}
if !xok && !yok {
lt, gt = x.(string) < y.(string), x.(string) > y.(string)
_x, xok := x.(float64)
_y, yok := y.(float64)
if xok && yok {
xScore = _x
yScore = _y
}
}
if xok != yok {
lt, gt = false, false
}
if !lt && !gt {
if xScore == yScore {
continue
}
if op.Order == datastore.SortOrderAscending {
return lt
return xScore < yScore
}
return gt
return xScore > yScore
}
return true
}

View File

@@ -95,14 +95,14 @@ var _ = Describe("Test kubeapi datastore driver", func() {
It("Test batch add function", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Namespace: "test-namespace", Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Namespace: "test-namespace2", Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
}
err := kubeStore.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
var datas2 = []datastore.Entity{
&model.Application{Namespace: "test-namespace", Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = kubeStore.BatchAdd(context.TODO(), datas2)
@@ -124,17 +124,17 @@ var _ = Describe("Test kubeapi datastore driver", func() {
})
It("Test index", func() {
var app = model.Application{
Namespace: "test",
Name: "test",
}
selector, err := labels.Parse(fmt.Sprintf("table=%s", app.TableName()))
Expect(err).ToNot(HaveOccurred())
Expect(cmp.Diff(app.Index()["namespace"], "test")).Should(BeEmpty())
Expect(cmp.Diff(app.Index()["name"], "test")).Should(BeEmpty())
for k, v := range app.Index() {
rq, err := labels.NewRequirement(k, selection.Equals, []string{v})
Expect(err).ToNot(HaveOccurred())
selector = selector.Add(*rq)
}
Expect(cmp.Diff(selector.String(), "namespace=test,table=vela_application")).Should(BeEmpty())
Expect(cmp.Diff(selector.String(), "name=test,table=vela_application")).Should(BeEmpty())
})
It("Test list function", func() {
var app model.Application
@@ -157,12 +157,6 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
app.Namespace = "test-namespace"
list, err = kubeStore.List(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -175,14 +169,26 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(kubeStore.Add(context.TODO(), &model.Cluster{Name: name})).Should(Succeed())
time.Sleep(time.Millisecond * 100)
}
entities, err := kubeStore.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderAscending}}})
entities, err := kubeStore.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderAscending}}})
Expect(err).Should(Succeed())
Expect(len(entities)).Should(Equal(3))
for i, name := range []string{"first", "second", "third"} {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = kubeStore.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderDescending}},
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
Page: 1,
PageSize: 2,
})
Expect(err).Should(Succeed())
Expect(len(entities)).Should(Equal(2))
for i, name := range []string{"third", "second"} {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = kubeStore.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
Page: 2,
PageSize: 2,
})
@@ -192,7 +198,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = kubeStore.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderDescending}},
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
FilterOptions: datastore.FilterOptions{
Queries: []datastore.FuzzyQueryOption{{Key: "name", Query: "ir"}},
},
@@ -210,11 +216,6 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(4)))
app.Namespace = "test-namespace"
count, err = kubeStore.Count(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(1)))
count, err = kubeStore.Count(context.TODO(), &model.Cluster{}, &datastore.FilterOptions{
Queries: []datastore.FuzzyQueryOption{{Key: "name", Query: "ir"}},
})

View File

@@ -219,7 +219,11 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
if op != nil && len(op.SortBy) > 0 {
_d := bson.D{}
for _, sortOp := range op.SortBy {
_d = append(_d, bson.E{Key: strings.ToLower(sortOp.Key), Value: int(sortOp.Order)})
key := strings.ToLower(sortOp.Key)
if key == "createtime" || key == "updatetime" {
key = "basemodel." + key
}
_d = append(_d, bson.E{Key: key, Value: int(sortOp.Order)})
}
findOptions.SetSort(_d)
}

View File

@@ -63,14 +63,14 @@ var _ = Describe("Test mongodb datastore driver", func() {
It("Test batch add function", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Namespace: "test-namespace", Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Namespace: "test-namespace2", Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
}
err := mongodbDriver.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
var datas2 = []datastore.Entity{
&model.Application{Namespace: "test-namespace", Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = mongodbDriver.BatchAdd(context.TODO(), datas2)
@@ -111,12 +111,6 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
app.Namespace = "test-namespace"
list, err = mongodbDriver.List(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -129,14 +123,25 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(mongodbDriver.Add(context.TODO(), &model.Cluster{Name: name})).Should(Succeed())
time.Sleep(time.Millisecond * 100)
}
entities, err := mongodbDriver.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderAscending}}})
entities, err := mongodbDriver.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderAscending}}})
Expect(err).Should(Succeed())
Expect(len(entities)).Should(Equal(3))
for i, name := range []string{"first", "second", "third"} {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = mongodbDriver.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderDescending}},
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
Page: 1,
PageSize: 2,
})
Expect(err).Should(Succeed())
Expect(len(entities)).Should(Equal(2))
for i, name := range []string{"third", "second"} {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = mongodbDriver.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
Page: 2,
PageSize: 2,
})
@@ -146,7 +151,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(entities[i].(*model.Cluster).Name).Should(Equal(name))
}
entities, err = mongodbDriver.List(context.TODO(), &model.Cluster{}, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderDescending}},
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
FilterOptions: datastore.FilterOptions{
Queries: []datastore.FuzzyQueryOption{{Key: "name", Query: "ir"}},
},
@@ -164,11 +169,6 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(4)))
app.Namespace = "test-namespace"
count, err = mongodbDriver.Count(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(1)))
count, err = mongodbDriver.Count(context.TODO(), &model.Cluster{}, &datastore.FilterOptions{
Queries: []datastore.FuzzyQueryOption{{Key: "name", Query: "ir"}},
})

View File

@@ -1,47 +0,0 @@
/*
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 model
import "github.com/oam-dev/kubevela/pkg/addon"
// AddonRegistry defines the data model of a AddonRegistry
type AddonRegistry struct {
Model
Name string `json:"name"`
Git *addon.GitAddonSource `json:"git,omitempty"`
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
}
// TableName return custom table name
func (a *AddonRegistry) TableName() string {
return tableNamePrefix + "addon_registry"
}
// PrimaryKey return custom primary key
func (a *AddonRegistry) PrimaryKey() string {
return a.Name
}
// Index return custom index
func (a *AddonRegistry) Index() map[string]string {
index := make(map[string]string)
if a.Name != "" {
index["name"] = a.Name
}
return index
}

View File

@@ -24,16 +24,15 @@ import (
)
func init() {
RegistModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{})
RegistModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{}, &ApplicationTrigger{})
}
// Application application delivery model
type Application struct {
Model
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Project string `json:"project"`
Namespace string `json:"namespace"`
Description string `json:"description"`
Icon string `json:"icon"`
Labels map[string]string `json:"labels,omitempty"`
@@ -55,9 +54,6 @@ func (a *Application) Index() map[string]string {
if a.Name != "" {
index["name"] = a.Name
}
if a.Namespace != "" {
index["namespace"] = a.Namespace
}
if a.Project != "" {
index["project"] = a.Project
}
@@ -78,7 +74,7 @@ type ComponentSelector struct {
// ApplicationComponent component database model
type ApplicationComponent struct {
Model
BaseModel
AppPrimaryKey string `json:"appPrimaryKey"`
Description string `json:"description,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
@@ -128,7 +124,7 @@ func (a *ApplicationComponent) Index() map[string]string {
// ApplicationPolicy app policy
type ApplicationPolicy struct {
Model
BaseModel
AppPrimaryKey string `json:"appPrimaryKey"`
Name string `json:"name"`
Description string `json:"description"`
@@ -192,7 +188,7 @@ var RevisionStatusRollback = "rollback"
// ApplicationRevision be created when an application initiates deployment and describes the phased version of the application.
type ApplicationRevision struct {
Model
BaseModel
AppPrimaryKey string `json:"appPrimaryKey"`
Version string `json:"version"`
RollbackVersion string `json:"rollbackVersion,omitempty"`
@@ -215,6 +211,18 @@ type ApplicationRevision struct {
WorkflowName string `json:"workflowName"`
// EnvName is the env name of this application revision
EnvName string `json:"envName"`
// CodeInfo is the code info of this application revision
CodeInfo *CodeInfo `json:"codeInfo,omitempty"`
}
// CodeInfo is the code info for webhook request
type CodeInfo struct {
// Commit is the commit hash
Commit string `json:"commit,omitempty"`
// Branch is the branch name
Branch string `json:"branch,omitempty"`
// User is the user name
User string `json:"user,omitempty"`
}
// TableName return custom table name
@@ -253,3 +261,51 @@ func (a *ApplicationRevision) Index() map[string]string {
}
return index
}
// ApplicationTrigger is the model for trigger
type ApplicationTrigger struct {
BaseModel
AppPrimaryKey string `json:"appPrimaryKey"`
WorkflowName string `json:"workflowName,omitempty"`
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
Description string `json:"description,omitempty"`
Token string `json:"token"`
Type string `json:"type"`
PayloadType string `json:"payloadType"`
}
const (
// PayloadTypeCustom is the payload type custom
PayloadTypeCustom = "custom"
// PayloadTypeDockerhub is the payload type dockerhub
PayloadTypeDockerhub = "dockerhub"
)
// TableName return custom table name
func (w *ApplicationTrigger) TableName() string {
return tableNamePrefix + "trigger"
}
// PrimaryKey return custom primary key
func (w *ApplicationTrigger) PrimaryKey() string {
return w.Token
}
// Index return custom index
func (w *ApplicationTrigger) Index() map[string]string {
index := make(map[string]string)
if w.AppPrimaryKey != "" {
index["appPrimaryKey"] = w.AppPrimaryKey
}
if w.Token != "" {
index["token"] = w.Token
}
if w.Name != "" {
index["name"] = w.Name
}
if w.Type != "" {
index["type"] = w.Type
}
return index
}

View File

@@ -52,7 +52,7 @@ var (
// Cluster describes the model of cluster in apiserver
type Cluster struct {
Model `json:"model"`
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description"`

View File

@@ -0,0 +1,63 @@
/*
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 model
func init() {
RegistModel(&Env{})
}
// Env models the data of env in database
type Env struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
// Project defines the project this Env belongs to
Project string `json:"project"`
// Namespace defines the K8s namespace of the Env in control plane
Namespace string `json:"namespace"`
// Targets defines the name of delivery target that belongs to this env
// In one project, a delivery target can only belong to one env.
Targets []string `json:"targets,omitempty"`
}
// TableName return custom table name
func (p *Env) TableName() string {
return tableNamePrefix + "env"
}
// PrimaryKey return custom primary key
func (p *Env) PrimaryKey() string {
return p.Name
}
// Index return custom index
func (p *Env) Index() map[string]string {
index := make(map[string]string)
if p.Name != "" {
index["name"] = p.Name
}
if p.Namespace != "" {
index["namespace"] = p.Namespace
}
if p.Project != "" {
index["project"] = p.Project
}
return index
}

View File

@@ -24,14 +24,25 @@ func init() {
// EnvBinding application env binding
type EnvBinding struct {
Model
AppPrimaryKey string `json:"appPrimaryKey"`
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
TargetNames []string `json:"targetNames"`
ComponentSelector *ComponentSelector `json:"componentSelector"`
//TODO: componentPatchs
BaseModel
AppPrimaryKey string `json:"appPrimaryKey"`
Name string `json:"name"`
ComponentsPatch []ComponentPatch `json:"componentsPatchs"`
}
// ComponentPatch Define differential patches for components in the environment.
type ComponentPatch struct {
Name string `json:"name"`
Properties *JSONStruct `json:"properties,omitempty"`
Disable bool `json:"disable"`
TraitsPatch []TraitPatch `json:"traitsPatch,omitempty"`
}
// TraitPatch Define differential patches for traits in the environment.
type TraitPatch struct {
Type string `json:"type"`
Properties *JSONStruct `json:"properties,omitempty"`
Disable bool `json:"disable"`
}
// TableName return custom table name

View File

@@ -113,19 +113,19 @@ func (j *JSONStruct) RawExtension() *runtime.RawExtension {
return &runtime.RawExtension{Raw: b}
}
// Model common model
type Model struct {
// BaseModel common model
type BaseModel struct {
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// SetCreateTime set create time
func (m *Model) SetCreateTime(time time.Time) {
func (m *BaseModel) SetCreateTime(time time.Time) {
m.CreateTime = time
}
// SetUpdateTime set update time
func (m *Model) SetUpdateTime(time time.Time) {
func (m *BaseModel) SetUpdateTime(time time.Time) {
m.UpdateTime = time
}

View File

@@ -22,12 +22,10 @@ func init() {
// Project project model
type Project struct {
Model
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
// Namespace Control cluster namespace
Namespace string `json:"namespace"`
}
// TableName return custom table name
@@ -46,8 +44,5 @@ func (p *Project) Index() map[string]string {
if p.Name != "" {
index["name"] = p.Name
}
if p.Namespace != "" {
index["namespace"] = p.Namespace
}
return index
}

View File

@@ -17,16 +17,14 @@ limitations under the License.
package model
func init() {
RegistModel(&DeliveryTarget{})
RegistModel(&Target{})
}
// DeliveryTarget defines the delivery target information for the application
// Target defines the delivery target information for the application
// It includes kubernetes clusters or cloud service providers
type DeliveryTarget struct {
Model
type Target struct {
BaseModel
Name string `json:"name"`
Project string `json:"project"`
Namespace string `json:"namespace"`
Alias string `json:"alias,omitempty"`
Description string `json:"description,omitempty"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
@@ -34,31 +32,25 @@ type DeliveryTarget struct {
}
// TableName return custom table name
func (d *DeliveryTarget) TableName() string {
return tableNamePrefix + "delivery_target"
func (d *Target) TableName() string {
return tableNamePrefix + "target"
}
// PrimaryKey return custom primary key
func (d *DeliveryTarget) PrimaryKey() string {
func (d *Target) PrimaryKey() string {
return d.Name
}
// Index return custom index
func (d *DeliveryTarget) Index() map[string]string {
func (d *Target) Index() map[string]string {
index := make(map[string]string)
if d.Name != "" {
index["name"] = d.Name
}
if d.Namespace != "" {
index["namespace"] = d.Namespace
}
if d.Project != "" {
index["project"] = d.Project
}
return index
}
// ClusterTarget kubernetes delivery target
// ClusterTarget one kubernetes cluster delivery target
type ClusterTarget struct {
ClusterName string `json:"clusterName" validate:"checkname"`
Namespace string `json:"namespace" optional:"true"`

View File

@@ -31,7 +31,7 @@ func init() {
// Workflow application delivery database model
type Workflow struct {
Model
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description"`
@@ -87,7 +87,7 @@ func (w *Workflow) Index() map[string]string {
// WorkflowRecord is the workflow record database model
type WorkflowRecord struct {
Model
BaseModel
WorkflowName string `json:"workflowName"`
WorkflowAlias string `json:"workflowAlias"`
AppPrimaryKey string `json:"appPrimaryKey"`

View File

@@ -19,12 +19,11 @@ package v1
import (
"time"
"github.com/oam-dev/kubevela/pkg/addon"
"github.com/getkin/kin-openapi/openapi3"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/addon"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/cloudprovider"
@@ -35,8 +34,8 @@ var (
CtxKeyApplication = "application"
// CtxKeyWorkflow request context key of workflow
CtxKeyWorkflow = "workflow"
// CtxKeyDeliveryTarget request context key of workflow
CtxKeyDeliveryTarget = "delivery-target"
// CtxKeyTarget request context key of workflow
CtxKeyTarget = "delivery-target"
// CtxKeyApplicationEnvBinding request context key of env binding
CtxKeyApplicationEnvBinding = "envbinding-policy"
// CtxKeyApplicationComponent request context key of component
@@ -101,15 +100,27 @@ type EnableAddonRequest struct {
// ListAddonResponse defines the format for addon list response
type ListAddonResponse struct {
Addons []*addon.Meta `json:"addons"`
Addons []*AddonInfo `json:"addons"`
// Message demonstrate the error info if exists
Message string `json:"message,omitempty"`
}
// AddonInfo contain addon metaData and some baseInfo
type AddonInfo struct {
*addon.Meta
RegistryName string `json:"registryName"`
}
// ListEnabledAddonResponse defines the format for enabled addon list response
type ListEnabledAddonResponse struct {
EnabledAddons []*AddonStatusResponse
EnabledAddons []*AddonBaseStatus `json:"enabledAddons"`
}
// AddonBaseStatus addon base status
type AddonBaseStatus struct {
Name string `json:"name"`
Phase AddonPhase `json:"phase"`
}
// DetailAddonResponse defines the format for showing the addon details
@@ -120,8 +131,9 @@ type DetailAddonResponse struct {
UISchema []*utils.UIParameter `json:"uiSchema"`
// More details about the addon, e.g. README
Detail string `json:"detail,omitempty"`
Definitions []*AddonDefinition `json:"definitions"`
Detail string `json:"detail,omitempty"`
Definitions []*AddonDefinition `json:"definitions"`
RegistryName string `json:"registryName,omitempty"`
}
// AddonDefinition is definition an addon can provide
@@ -134,12 +146,12 @@ type AddonDefinition struct {
// AddonStatusResponse defines the format of addon status response
type AddonStatusResponse struct {
Name string `json:"name"`
Phase AddonPhase `json:"phase"`
Args map[string]string `json:"args"`
AddonBaseStatus
Args map[string]string `json:"args"`
EnablingProgress *EnablingProgress `json:"enabling_progress,omitempty"`
AppStatus common.AppStatus `json:"appStatus,omitempty"`
// the status of multiple clusters
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
}
// EnablingProgress defines the progress of enabling an addon
@@ -265,9 +277,10 @@ type ClusterBase struct {
Reason string `json:"reason"`
}
// ListApplicatioOptions list application query options
type ListApplicatioOptions struct {
// ListApplicationOptions list application query options
type ListApplicationOptions struct {
Project string `json:"project"`
Env string `json:"env"`
TargetName string `json:"targetName"`
Query string `json:"query"`
}
@@ -280,16 +293,6 @@ type ListApplicationResponse struct {
// EnvBindingList env binding list
type EnvBindingList []*EnvBinding
// ContainTarget contain cluster name
func (e EnvBindingList) ContainTarget(name string) bool {
for _, eb := range e {
if utils.StringsContain(eb.TargetNames, name) {
return true
}
}
return false
}
// ApplicationBase application base model
type ApplicationBase struct {
Name string `json:"name"`
@@ -310,13 +313,13 @@ type ApplicationStatusResponse struct {
// ApplicationStatisticsResponse application statistics response body
type ApplicationStatisticsResponse struct {
EnvCount int64 `json:"envCount"`
DeliveryTargetCount int64 `json:"deliveryTargetCount"`
RevisonCount int64 `json:"revisonCount"`
WorkflowCount int64 `json:"workflowCount"`
EnvCount int64 `json:"envCount"`
TargetCount int64 `json:"targetCount"`
RevisonCount int64 `json:"revisonCount"`
WorkflowCount int64 `json:"workflowCount"`
}
// CreateApplicationRequest create application request body
// CreateApplicationRequest create application request body
type CreateApplicationRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
@@ -325,11 +328,10 @@ type CreateApplicationRequest struct {
Icon string `json:"icon"`
Labels map[string]string `json:"labels,omitempty"`
EnvBinding []*EnvBinding `json:"envBinding,omitempty"`
YamlConfig string `json:"yamlConfig,omitempty"`
Component *CreateComponentRequest `json:"component"`
}
// UpdateApplicationRequest update application base config
// UpdateApplicationRequest update application base config
type UpdateApplicationRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
@@ -337,13 +339,44 @@ type UpdateApplicationRequest struct {
Labels map[string]string `json:"labels,omitempty"`
}
// CreateApplicationTriggerRequest create application trigger
type CreateApplicationTriggerRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
WorkflowName string `json:"workflowName"`
Type string `json:"type" validate:"oneof=webhook"`
PayloadType string `json:"payloadType" validate:"oneof=custom"`
}
// ApplicationTriggerBase application trigger base model
type ApplicationTriggerBase struct {
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
Description string `json:"description,omitempty"`
WorkflowName string `json:"workflowName"`
Type string `json:"type"`
PayloadType string `json:"payloadType"`
Token string `json:"token"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// ListApplicationTriggerResponse list application triggers response body
type ListApplicationTriggerResponse struct {
Triggers []*ApplicationTriggerBase `json:"triggers"`
}
// HandleApplicationWebhookRequest handles application webhook request
type HandleApplicationWebhookRequest struct {
Upgrade map[string]*model.JSONStruct `json:"upgrade,omitempty"`
CodeInfo *model.CodeInfo `json:"codeInfo,omitempty"`
}
// EnvBinding application env binding
type EnvBinding struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"`
Name string `json:"name" validate:"checkname"`
//TODO: support componentsPatch
}
// EnvBindingTarget the target struct in the envbinding base struct
@@ -354,15 +387,16 @@ type EnvBindingTarget struct {
// EnvBindingBase application env binding
type EnvBindingBase struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
Targets []EnvBindingTarget `json:"deliveryTargets,omitempty"`
ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
AppDeployName string `json:"appDeployName"`
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
Targets []EnvBindingTarget `json:"targets,omitempty"`
ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
AppDeployName string `json:"appDeployName"`
AppDeployNamespace string `json:"appDeployNamespace"`
}
// DetailEnvBindingResponse defines the response of env-binding details
@@ -486,7 +520,6 @@ type ProjectBase struct {
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description"`
Namespace string `json:"namespace"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
@@ -496,7 +529,60 @@ type CreateProjectRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Namespace string `json:"namespace" optional:"true"`
}
// Env models the data of env in API
type Env struct {
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty" optional:"true"`
// Project defines the project this Env belongs to
Project NameAlias `json:"project"`
// Namespace defines the K8s namespace of the Env in control plane
Namespace string `json:"namespace"`
// Targets defines the name of delivery target that belongs to this env
// In one project, a delivery target can only belong to one env.
Targets []NameAlias `json:"targets,omitempty" optional:"true"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// ListEnvOptions list envs by query options
type ListEnvOptions struct {
Project string `json:"project"`
}
// ListEnvResponse response the while env list
type ListEnvResponse struct {
Envs []*Env `json:"envs"`
}
// CreateEnvRequest contains the env data as request body
type CreateEnvRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
// Project defines the project this Env belongs to
Project string `json:"project"`
// Namespace defines the K8s namespace of the Env in control plane
Namespace string `json:"namespace"`
// Targets defines the name of delivery target that belongs to this env
// In one project, a delivery target can only belong to one env.
Targets []string `json:"targets,omitempty" optional:"true"`
}
// UpdateEnvRequest defines the data of Env for update
type UpdateEnvRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
// Targets defines the name of delivery target that belongs to this env
// In one project, a delivery target can only belong to one env.
Targets []string `json:"targets,omitempty" optional:"true"`
}
// ListDefinitionResponse list definition response model
@@ -589,8 +675,7 @@ type UpdateWorkflowRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Steps []WorkflowStep `json:"steps,omitempty"`
Enable bool `json:"enable"`
Default bool `json:"default"`
Default *bool `json:"default"`
}
// WorkflowStep workflow step config
@@ -635,13 +720,22 @@ type ListWorkflowRecordsResponse struct {
Total int64 `json:"total"`
}
const (
// TriggerTypeWeb means trigger by web
TriggerTypeWeb string = "web"
// TriggerTypeAPI means trigger by api
TriggerTypeAPI string = "api"
// TriggerTypeWebhook means trigger by webhook
TriggerTypeWebhook string = "webhook"
)
// DetailWorkflowRecordResponse get workflow record detail
type DetailWorkflowRecordResponse struct {
WorkflowRecord
DeployTime time.Time `json:"deployTime"`
DeployUser string `json:"deployUser"`
Note string `json:"note"`
// TriggerType the event trigger source, Web or API
// TriggerType the event trigger source, Web or API or Webhook
TriggerType string `json:"triggerType"`
}
@@ -662,10 +756,12 @@ type ApplicationDeployRequest struct {
WorkflowName string `json:"workflowName"`
// User note message, optional
Note string `json:"note"`
// TriggerType the event trigger source, Web or API
TriggerType string `json:"triggerType" validate:"oneof=web api"`
// TriggerType the event trigger source, Web or API or Webhook
TriggerType string `json:"triggerType" validate:"oneof=web api webhook"`
// Force set to True to ignore unfinished events.
Force bool `json:"force"`
// CodeInfo is the source code info of this deploy
CodeInfo *model.CodeInfo `json:"gitInfo,omitempty"`
}
// ApplicationDeployResponse application deploy response body
@@ -676,12 +772,8 @@ type ApplicationDeployResponse struct {
// VelaQLViewResponse query response
type VelaQLViewResponse map[string]interface{}
// PutApplicationEnvRequest set diff request
type PutApplicationEnvRequest struct {
ComponentSelector *ComponentSelector `json:"componentSelector,omitempty"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
// PutApplicationEnvBindingRequest update app envbinding request body
type PutApplicationEnvBindingRequest struct {
}
// ListApplicationEnvBinding list app envBindings
@@ -689,8 +781,8 @@ type ListApplicationEnvBinding struct {
EnvBindings []*EnvBindingBase `json:"envBindings"`
}
// CreateApplicationEnvRequest new application env
type CreateApplicationEnvRequest struct {
// CreateApplicationEnvbindingRequest new application env
type CreateApplicationEnvbindingRequest struct {
EnvBinding
}
@@ -721,21 +813,19 @@ type ApplicationTrait struct {
UpdateTime time.Time `json:"updateTime"`
}
// CreateDeliveryTargetRequest create delivery target request body
type CreateDeliveryTargetRequest struct {
// CreateTargetRequest create delivery target request body
type CreateTargetRequest struct {
Name string `json:"name" validate:"checkname"`
Project string `json:"project" validate:"checkname"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
Variable map[string]interface{} `json:"variable,omitempty"`
}
// UpdateDeliveryTargetRequest only support full quantity update
type UpdateDeliveryTargetRequest struct {
// UpdateTargetRequest only support full quantity update
type UpdateTargetRequest struct {
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
Variable map[string]interface{} `json:"variable,omitempty"`
}
@@ -745,21 +835,20 @@ type ClusterTarget struct {
Namespace string `json:"namespace" optional:"true"`
}
// DetailDeliveryTargetResponse detail deliveryTarget response
type DetailDeliveryTargetResponse struct {
DeliveryTargetBase
// DetailTargetResponse detail Target response
type DetailTargetResponse struct {
TargetBase
}
// ListTargetResponse list delivery target response body
type ListTargetResponse struct {
Targets []DeliveryTargetBase `json:"targets"`
Total int64 `json:"total"`
Targets []TargetBase `json:"targets"`
Total int64 `json:"total"`
}
// DeliveryTargetBase deliveryTarget base model
type DeliveryTargetBase struct {
// TargetBase Target base model
type TargetBase struct {
Name string `json:"name"`
Project *ProjectBase `json:"project"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
@@ -775,12 +864,14 @@ type ApplicationRevisionBase struct {
CreateTime time.Time `json:"createTime"`
Version string `json:"version"`
Status string `json:"status"`
Reason string `json:"reason"`
DeployUser string `json:"deployUser"`
Reason string `json:"reason,omitempty"`
DeployUser string `json:"deployUser,omitempty"`
Note string `json:"note"`
EnvName string `json:"envName"`
// SourceType the event trigger source, Web or API
// SourceType the event trigger source, Web or API or Webhook
TriggerType string `json:"triggerType"`
// CodeInfo is the code info of this application revision
CodeInfo *model.CodeInfo `json:"codeInfo,omitempty"`
}
// ListRevisionsResponse list application revisions

View File

@@ -37,6 +37,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/webservice"
)
@@ -154,7 +155,7 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
}
func (s restServer) runLeader(ctx context.Context, duration time.Duration) {
w := usecase.NewWorkflowUsecase(s.dataStore)
w := usecase.NewWorkflowUsecase(s.dataStore, usecase.NewEnvUsecase(s.dataStore))
t := time.NewTicker(duration)
defer t.Stop()
@@ -190,8 +191,11 @@ func (s *restServer) RegisterServices() restfulspec.Config {
// Add container filter to respond to OPTIONS
s.webContainer.Filter(s.webContainer.OPTIONSFilter)
// Add request log
s.webContainer.Filter(s.requestLog)
// Regist all custom webservice
for _, handler := range webservice.GetRegistedWebService() {
for _, handler := range webservice.GetRegisteredWebService() {
s.webContainer.Add(handler.GetWebService())
}
@@ -203,6 +207,22 @@ func (s *restServer) RegisterServices() restfulspec.Config {
return config
}
func (s *restServer) requestLog(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
start := time.Now()
c := utils.NewResponseCapture(resp.ResponseWriter)
resp.ResponseWriter = c
chain.ProcessFilter(req, resp)
takeTime := time.Since(start)
log.Logger.With(
"clientIP", utils.ClientIP(req.Request),
"path", req.Request.URL.Path,
"method", req.Request.Method,
"status", c.StatusCode(),
"time", takeTime.String(),
"responseSize", len(c.Bytes()),
).Infof("request log")
}
func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &spec.Info{
InfoProps: spec.InfoProps{

View File

@@ -39,10 +39,10 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
restutils "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/apply"
velaerr "github.com/oam-dev/kubevela/pkg/utils/errors"
)
// AddonHandler handle CRUD and installation of addons
@@ -57,7 +57,7 @@ type AddonHandler interface {
GetAddon(ctx context.Context, name string, registry string) (*apis.DetailAddonResponse, error)
EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error
DisableAddon(ctx context.Context, name string) error
ListEnabledAddon(ctx context.Context) ([]*apis.AddonStatusResponse, error)
ListEnabledAddon(ctx context.Context) ([]*apis.AddonBaseStatus, error)
UpdateAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error
}
@@ -78,11 +78,12 @@ func AddonImpl2AddonRes(impl *pkgaddon.UIData) (*apis.DetailAddonResponse, error
})
}
return &apis.DetailAddonResponse{
Meta: impl.Meta,
APISchema: impl.APISchema,
UISchema: impl.UISchema,
Detail: impl.Detail,
Definitions: defs,
Meta: impl.Meta,
APISchema: impl.APISchema,
UISchema: impl.UISchema,
Detail: impl.Detail,
Definitions: defs,
RegistryName: impl.RegistryName,
}, nil
}
@@ -163,7 +164,6 @@ func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registr
}
func (u *defaultAddonHandler) StatusAddon(ctx context.Context, name string) (*apis.AddonStatusResponse, error) {
status, err := pkgaddon.GetAddonStatus(ctx, u.kubeClient, name)
if err != nil {
return nil, bcode.ErrGetAddonApplication
@@ -171,14 +171,20 @@ func (u *defaultAddonHandler) StatusAddon(ctx context.Context, name string) (*ap
if status.AddonPhase == string(apis.AddonPhaseDisabled) {
return &apis.AddonStatusResponse{
Phase: apis.AddonPhase(status.AddonPhase),
AddonBaseStatus: apis.AddonBaseStatus{
Name: name,
Phase: apis.AddonPhase(status.AddonPhase),
},
}, nil
}
res := apis.AddonStatusResponse{
Name: name,
Phase: apis.AddonPhase(status.AddonPhase),
AddonBaseStatus: apis.AddonBaseStatus{
Name: name,
Phase: apis.AddonPhase(status.AddonPhase),
},
AppStatus: *status.AppStatus,
Clusters: status.Clusters,
}
if res.Phase != apis.AddonPhaseEnabled {
@@ -196,8 +202,8 @@ func (u *defaultAddonHandler) StatusAddon(ctx context.Context, name string) (*ap
for k, v := range sec.Data {
res.Args[k] = string(v)
}
}
return &res, nil
}
@@ -208,7 +214,7 @@ func (u *defaultAddonHandler) ListAddons(ctx context.Context, registry, query st
return nil, err
}
var gatherErr restutils.GatherErr
var gatherErr velaerr.ErrorList
for _, r := range rs {
if registry != "" && r.Name != registry {
@@ -255,7 +261,10 @@ func (u *defaultAddonHandler) ListAddons(ctx context.Context, registry, query st
}
addonResources = append(addonResources, addonRes)
}
return addonResources, gatherErr
if gatherErr.HasError() {
return addonResources, gatherErr
}
return addonResources, nil
}
func (u *defaultAddonHandler) DeleteAddonRegistry(ctx context.Context, name string) error {
@@ -358,18 +367,18 @@ func (u *defaultAddonHandler) DisableAddon(ctx context.Context, name string) err
return nil
}
func (u *defaultAddonHandler) ListEnabledAddon(ctx context.Context) ([]*apis.AddonStatusResponse, error) {
func (u *defaultAddonHandler) ListEnabledAddon(ctx context.Context) ([]*apis.AddonBaseStatus, error) {
apps := &v1beta1.ApplicationList{}
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil {
return nil, err
}
var response []*apis.AddonStatusResponse
var response []*apis.AddonBaseStatus
for _, application := range apps.Items {
if addonName := application.Labels[oam.LabelAddonName]; addonName != "" {
if application.Status.Phase != common2.ApplicationRunning {
continue
}
response = append(response, &apis.AddonStatusResponse{
response = append(response, &apis.AddonBaseStatus{
Name: addonName,
Phase: convertAppStateToAddonPhase(application.Status.Phase),
})
@@ -426,6 +435,9 @@ func mergeAddons(a1, a2 []*pkgaddon.UIData) []*pkgaddon.UIData {
}
func hasAddon(addons []*pkgaddon.UIData, name string) bool {
if name == "" {
return true
}
for _, addon := range addons {
if addon.Name == name {
return true

View File

@@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"time"
@@ -57,11 +58,13 @@ const (
// EnvBindingPolicyDefaultName default policy name
EnvBindingPolicyDefaultName string = "env-bindings"
defaultTokenLen int = 16
)
// ApplicationUsecase application usecase
type ApplicationUsecase interface {
ListApplications(ctx context.Context, listOptions apisv1.ListApplicatioOptions) ([]*apisv1.ApplicationBase, error)
ListApplications(ctx context.Context, listOptions apisv1.ListApplicationOptions) ([]*apisv1.ApplicationBase, error)
GetApplication(ctx context.Context, appName string) (*model.Application, error)
GetApplicationStatus(ctx context.Context, app *model.Application, envName string) (*common.AppStatus, error)
DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error)
@@ -88,24 +91,28 @@ type ApplicationUsecase interface {
DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error)
Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error)
ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error)
CreateApplicationTrigger(ctx context.Context, app *model.Application, req apisv1.CreateApplicationTriggerRequest) (*apisv1.ApplicationTriggerBase, error)
ListApplicationTriggers(ctx context.Context, app *model.Application) ([]*apisv1.ApplicationTriggerBase, error)
}
type applicationUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
apply apply.Applicator
workflowUsecase WorkflowUsecase
envBindingUsecase EnvBindingUsecase
deliveryTargetUsecase DeliveryTargetUsecase
definitionUsecase DefinitionUsecase
projectUsecase ProjectUsecase
ds datastore.DataStore
kubeClient client.Client
apply apply.Applicator
workflowUsecase WorkflowUsecase
envUsecase EnvUsecase
envBindingUsecase EnvBindingUsecase
targetUsecase TargetUsecase
definitionUsecase DefinitionUsecase
projectUsecase ProjectUsecase
}
// NewApplicationUsecase new application usecase
func NewApplicationUsecase(ds datastore.DataStore,
workflowUsecase WorkflowUsecase,
envBindingUsecase EnvBindingUsecase,
deliveryTargetUsecase DeliveryTargetUsecase,
envUsecase EnvUsecase,
targetUsecase TargetUsecase,
definitionUsecase DefinitionUsecase,
projectUsecase ProjectUsecase,
) ApplicationUsecase {
@@ -114,43 +121,82 @@ func NewApplicationUsecase(ds datastore.DataStore,
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
envBindingUsecase: envBindingUsecase,
deliveryTargetUsecase: deliveryTargetUsecase,
kubeClient: kubecli,
apply: apply.NewAPIApplicator(kubecli),
definitionUsecase: definitionUsecase,
projectUsecase: projectUsecase,
ds: ds,
workflowUsecase: workflowUsecase,
envBindingUsecase: envBindingUsecase,
targetUsecase: targetUsecase,
kubeClient: kubecli,
apply: apply.NewAPIApplicator(kubecli),
definitionUsecase: definitionUsecase,
projectUsecase: projectUsecase,
envUsecase: envUsecase,
}
}
// ListApplications list applications
func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptions apisv1.ListApplicatioOptions) ([]*apisv1.ApplicationBase, error) {
func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.ListApplicationOptions) ([]*model.Application, error) {
var app = model.Application{}
if listOptions.Project != "" {
app.Project = listOptions.Project
}
entitys, err := c.ds.List(ctx, &app, &datastore.ListOptions{})
var err error
var envBinding []*apisv1.EnvBindingBase
if listOptions.Env != "" || listOptions.TargetName != "" {
envBinding, err = listFullEnvBinding(ctx, ds, envListOption{})
if err != nil {
log.Logger.Errorf("list envbinding for list application in env %s err %v", listOptions.Env, err)
return nil, err
}
}
entities, err := ds.List(ctx, &app, &datastore.ListOptions{})
if err != nil {
return nil, err
}
var list []*apisv1.ApplicationBase
for _, entity := range entitys {
appModel := entity.(*model.Application)
appBase := c.converAppModelToBase(ctx, appModel)
var list []*model.Application
for _, entity := range entities {
appModel, ok := entity.(*model.Application)
if !ok {
continue
}
if listOptions.Query != "" &&
!(strings.Contains(appBase.Alias, listOptions.Query) ||
strings.Contains(appBase.Name, listOptions.Query) ||
strings.Contains(appBase.Description, listOptions.Query)) {
!(strings.Contains(appModel.Alias, listOptions.Query) ||
strings.Contains(appModel.Name, listOptions.Query) ||
strings.Contains(appModel.Description, listOptions.Query)) {
continue
}
if listOptions.TargetName != "" {
targetIsContain, _ := c.envBindingUsecase.CheckAppEnvBindingsContainTarget(ctx, appModel, listOptions.TargetName)
targetIsContain, _ := CheckAppEnvBindingsContainTarget(envBinding, listOptions.TargetName)
if !targetIsContain {
continue
}
}
if len(envBinding) > 0 && listOptions.Env != "" {
check := func() bool {
for _, eb := range envBinding {
if eb.Name == listOptions.Env && appModel.PrimaryKey() == eb.AppDeployName {
return true
}
}
return false
}
if !check() {
continue
}
}
list = append(list, appModel)
}
return list, nil
}
// ListApplications list applications
func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptions apisv1.ListApplicationOptions) ([]*apisv1.ApplicationBase, error) {
apps, err := listApp(ctx, c.ds, listOptions)
if err != nil {
return nil, err
}
var list []*apisv1.ApplicationBase
for _, app := range apps {
appBase := c.converAppModelToBase(ctx, app)
list = append(list, appBase)
}
sort.Slice(list, func(i, j int) bool {
@@ -176,7 +222,7 @@ func (c *applicationUsecaseImpl) GetApplication(ctx context.Context, appName str
// DetailApplication detail application info
func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error) {
base := c.converAppModelToBase(ctx, app)
policys, err := c.queryApplicationPolicys(ctx, app)
policys, err := c.queryApplicationPolicies(ctx, app)
if err != nil {
return nil, err
}
@@ -205,7 +251,7 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod
ComponentNum: componentNum,
},
ApplicationType: func() string {
if c.envBindingUsecase.GetSuitableType(ctx, app) == DeployCloudResource {
if GetSuitableDeployWay(ctx, c.kubeClient, c.ds, app) == DeployCloudResource {
return "cloud"
}
return "common"
@@ -217,7 +263,11 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod
// GetApplicationStatus get application status from controller cluster
func (c *applicationUsecaseImpl) GetApplicationStatus(ctx context.Context, appmodel *model.Application, envName string) (*common.AppStatus, error) {
var app v1beta1.Application
err := c.kubeClient.Get(ctx, types.NamespacedName{Namespace: appmodel.Namespace, Name: convertAppName(appmodel.Name, envName)}, &app)
env, err := c.envUsecase.GetEnv(ctx, envName)
if err != nil {
return nil, err
}
err = c.kubeClient.Get(ctx, types.NamespacedName{Namespace: env.Namespace, Name: appmodel.Name}, &app)
if err != nil {
if apierrors.IsNotFound(err) {
return nil, nil
@@ -230,7 +280,7 @@ func (c *applicationUsecaseImpl) GetApplicationStatus(ctx context.Context, appmo
return &app.Status, nil
}
// GetApplicationCR get application cr in cluster
// GetApplicationCR get application CR in cluster
func (c *applicationUsecaseImpl) GetApplicationCR(ctx context.Context, appModel *model.Application) (*v1beta1.ApplicationList, error) {
var apps v1beta1.ApplicationList
selector := labels.NewSelector()
@@ -241,7 +291,6 @@ func (c *applicationUsecaseImpl) GetApplicationCR(ctx context.Context, appModel
selector = selector.Add(*re)
err = c.kubeClient.List(ctx, &apps, &client.ListOptions{
LabelSelector: selector,
Namespace: appModel.Namespace,
})
if err != nil {
if apierrors.IsNotFound(err) {
@@ -282,29 +331,8 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
if err != nil {
return nil, err
}
application.Namespace = project.Namespace
application.Project = project.Name
if req.YamlConfig != "" {
var oamApp v1beta1.Application
if err := yaml.Unmarshal([]byte(req.YamlConfig), &oamApp); err != nil {
log.Logger.Errorf("application yaml config is invalid,%s", err.Error())
return nil, bcode.ErrApplicationConfig
}
// split the configuration and store it in the database.
if err := c.saveApplicationComponent(ctx, &application, oamApp.Spec.Components); err != nil {
log.Logger.Errorf("save applictaion component failure,%s", err.Error())
return nil, err
}
if len(oamApp.Spec.Policies) > 0 {
if err := c.saveApplicationPolicy(ctx, &application, oamApp.Spec.Policies); err != nil {
log.Logger.Errorf("save applictaion polocies failure,%s", err.Error())
return nil, err
}
}
}
if req.Component != nil {
_, err = c.AddComponent(ctx, &application, *req.Component)
if err != nil {
@@ -318,6 +346,14 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
if err != nil {
return nil, err
}
if _, err := c.CreateApplicationTrigger(ctx, &application, apisv1.CreateApplicationTriggerRequest{
Name: fmt.Sprintf("%s-%s", application.Name, "default"),
PayloadType: model.PayloadTypeCustom,
Type: apisv1.TriggerTypeWebhook,
WorkflowName: convertWorkflowName(req.EnvBinding[0].Name),
}); err != nil {
return nil, err
}
}
// add application to db.
if err := c.ds.Add(ctx, &application); err != nil {
@@ -331,6 +367,67 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
return base, nil
}
// CreateApplicationTrigger create application trigger
func (c *applicationUsecaseImpl) CreateApplicationTrigger(ctx context.Context, app *model.Application, req apisv1.CreateApplicationTriggerRequest) (*apisv1.ApplicationTriggerBase, error) {
trigger := &model.ApplicationTrigger{
AppPrimaryKey: app.Name,
WorkflowName: req.WorkflowName,
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Type: req.Type,
PayloadType: req.PayloadType,
Token: genWebhookToken(),
}
if err := c.ds.Add(ctx, trigger); err != nil {
log.Logger.Errorf("failed to create application trigger, %s", err.Error())
return nil, err
}
return &apisv1.ApplicationTriggerBase{
WorkflowName: req.WorkflowName,
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Type: req.Type,
PayloadType: req.PayloadType,
Token: trigger.Token,
}, nil
}
// ListApplicationTrigger list application triggers
func (c *applicationUsecaseImpl) ListApplicationTriggers(ctx context.Context, app *model.Application) ([]*apisv1.ApplicationTriggerBase, error) {
trigger := &model.ApplicationTrigger{
AppPrimaryKey: app.Name,
}
triggers, err := c.ds.List(ctx, trigger, &datastore.ListOptions{
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}},
)
if err != nil {
log.Logger.Errorf("failed to list application triggers, %s", err.Error())
return nil, err
}
resp := []*apisv1.ApplicationTriggerBase{}
for _, raw := range triggers {
trigger, ok := raw.(*model.ApplicationTrigger)
if ok {
resp = append(resp, &apisv1.ApplicationTriggerBase{
WorkflowName: trigger.WorkflowName,
Name: trigger.Name,
Alias: trigger.Alias,
Description: trigger.Description,
Type: trigger.Type,
PayloadType: trigger.PayloadType,
Token: trigger.Token,
UpdateTime: trigger.UpdateTime,
CreateTime: trigger.CreateTime,
})
}
}
return resp, nil
}
func (c *applicationUsecaseImpl) genPolicyByEnv(ctx context.Context, app *model.Application, envName string, components []*model.ApplicationComponent) (v1beta1.AppPolicy, error) {
appPolicy := v1beta1.AppPolicy{}
envBinding, err := c.envBindingUsecase.GetEnvBinding(ctx, app, envName)
@@ -339,14 +436,17 @@ func (c *applicationUsecaseImpl) genPolicyByEnv(ctx context.Context, app *model.
}
appPolicy.Name = genPolicyName(envBinding.Name)
appPolicy.Type = string(EnvBindingPolicy)
env, err := c.envUsecase.GetEnv(ctx, envName)
if err != nil {
return appPolicy, err
}
var envBindingSpec v1alpha1.EnvBindingSpec
for _, targetName := range envBinding.TargetNames {
target, err := c.deliveryTargetUsecase.GetDeliveryTarget(ctx, targetName)
for _, targetName := range env.Targets {
target, err := c.targetUsecase.GetTarget(ctx, targetName)
if err != nil || target == nil {
return appPolicy, bcode.ErrFoundEnvbindingDeliveryTarget
}
envBindingSpec.Envs = append(envBindingSpec.Envs, c.createTargetClusterEnv(ctx, app, envBinding, target, components))
envBindingSpec.Envs = append(envBindingSpec.Envs, c.createTargetClusterEnv(ctx, envBinding, env, target, components))
}
properties, err := model.NewJSONStructByStruct(envBindingSpec)
if err != nil {
@@ -375,45 +475,6 @@ func (c *applicationUsecaseImpl) UpdateApplication(ctx context.Context, app *mod
return c.converAppModelToBase(ctx, app), nil
}
func (c *applicationUsecaseImpl) saveApplicationComponent(ctx context.Context, app *model.Application, components []common.ApplicationComponent) error {
var componentModels []datastore.Entity
for _, component := range components {
// TODO: Check whether the component type is supported.
var traits []model.ApplicationTrait
for _, trait := range component.Traits {
properties, err := model.NewJSONStruct(trait.Properties)
if err != nil {
log.Logger.Errorf("parse trait properties failire %w", err)
return bcode.ErrInvalidProperties
}
traits = append(traits, model.ApplicationTrait{
Type: trait.Type,
Properties: properties,
})
}
properties, err := model.NewJSONStruct(component.Properties)
if err != nil {
log.Logger.Errorf("parse component properties failire %w", err)
return bcode.ErrInvalidProperties
}
componentModel := model.ApplicationComponent{
AppPrimaryKey: app.PrimaryKey(),
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
Inputs: component.Inputs,
Outputs: component.Outputs,
Scopes: component.Scopes,
Traits: traits,
Properties: properties,
}
componentModels = append(componentModels, &componentModel)
}
log.Logger.Infof("batch add %d components for app %s", len(componentModels), utils2.Sanitize(app.PrimaryKey()))
return c.ds.BatchAdd(ctx, componentModels)
}
// ListRecords list application record
func (c *applicationUsecaseImpl) ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error) {
var record = model.WorkflowRecord{
@@ -520,52 +581,18 @@ func (c *applicationUsecaseImpl) converComponentModelToBase(m *model.Application
// ListPolicies list application policies
func (c *applicationUsecaseImpl) ListPolicies(ctx context.Context, app *model.Application) ([]*apisv1.PolicyBase, error) {
policies, err := c.queryApplicationPolicys(ctx, app)
policies, err := c.queryApplicationPolicies(ctx, app)
if err != nil {
return nil, err
}
var list []*apisv1.PolicyBase
for _, policy := range policies {
list = append(list, c.converPolicyModelToBase(policy))
list = append(list, convertPolicyModelToBase(policy))
}
return list, nil
}
func (c *applicationUsecaseImpl) converPolicyModelToBase(policy *model.ApplicationPolicy) *apisv1.PolicyBase {
pb := &apisv1.PolicyBase{
Name: policy.Name,
Type: policy.Type,
Properties: policy.Properties,
Description: policy.Description,
Creator: policy.Creator,
CreateTime: policy.CreateTime,
UpdateTime: policy.UpdateTime,
}
return pb
}
func (c *applicationUsecaseImpl) saveApplicationPolicy(ctx context.Context, app *model.Application, policys []v1beta1.AppPolicy) error {
var policyModels []datastore.Entity
for _, policy := range policys {
properties, err := model.NewJSONStruct(policy.Properties)
if err != nil {
log.Logger.Errorf("parse trait properties failire %w", err)
return bcode.ErrInvalidProperties
}
appPolicy := &model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Name: policy.Name,
Type: policy.Type,
Properties: properties,
}
if policy.Type != string(EnvBindingPolicy) {
policyModels = append(policyModels, appPolicy)
}
}
return c.ds.BatchAdd(ctx, policyModels)
}
func (c *applicationUsecaseImpl) queryApplicationPolicys(ctx context.Context, app *model.Application) (list []*model.ApplicationPolicy, err error) {
func (c *applicationUsecaseImpl) queryApplicationPolicies(ctx context.Context, app *model.Application) (list []*model.ApplicationPolicy, err error) {
var policy = model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
}
@@ -592,7 +619,7 @@ func (c *applicationUsecaseImpl) DetailPolicy(ctx context.Context, app *model.Ap
return nil, err
}
return &apisv1.DetailPolicyResponse{
PolicyBase: *c.converPolicyModelToBase(&policy),
PolicyBase: *convertPolicyModelToBase(&policy),
}, nil
}
@@ -658,6 +685,7 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
TriggerType: req.TriggerType,
WorkflowName: oamApp.Annotations[oam.AnnotationWorkflowName],
EnvName: workflow.EnvName,
CodeInfo: req.CodeInfo,
}
if err := c.ds.Add(ctx, appRevision); err != nil {
@@ -697,14 +725,7 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
}
return &apisv1.ApplicationDeployResponse{
ApplicationRevisionBase: apisv1.ApplicationRevisionBase{
Version: appRevision.Version,
Status: appRevision.Status,
Reason: appRevision.Reason,
DeployUser: appRevision.DeployUser,
Note: appRevision.Note,
TriggerType: appRevision.TriggerType,
},
ApplicationRevisionBase: c.converRevisionModelToBase(appRevision),
}, nil
}
@@ -727,7 +748,10 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
if workflow == nil || workflow.EnvName == "" {
return nil, bcode.ErrWorkflowNotExist
}
env, err := c.envUsecase.GetEnv(ctx, workflow.EnvName)
if err != nil {
return nil, err
}
labels := make(map[string]string)
for key, value := range appModel.Labels {
labels[key] = value
@@ -740,8 +764,8 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: convertAppName(appModel.Name, workflow.EnvName),
Namespace: appModel.Namespace,
Name: appModel.Name,
Namespace: env.Namespace,
Labels: labels,
Annotations: map[string]string{
oam.AnnotationDeployVersion: version,
@@ -754,8 +778,8 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
}
originalApp := &v1beta1.Application{}
if err := c.kubeClient.Get(ctx, types.NamespacedName{
Name: convertAppName(appModel.Name, workflow.EnvName),
Namespace: appModel.Namespace,
Name: appModel.Name,
Namespace: env.Namespace,
}, originalApp); err == nil {
app.ResourceVersion = originalApp.ResourceVersion
}
@@ -793,7 +817,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
traits = append(traits, aTrait)
}
bc := common.ApplicationComponent{
Name: converComponentName(component.Name, workflow.EnvName),
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
@@ -868,6 +892,20 @@ func (c *applicationUsecaseImpl) converAppModelToBase(ctx context.Context, app *
return appBase
}
func (c *applicationUsecaseImpl) converRevisionModelToBase(revision *model.ApplicationRevision) apisv1.ApplicationRevisionBase {
return apisv1.ApplicationRevisionBase{
Version: revision.Version,
Status: revision.Status,
Reason: revision.Reason,
DeployUser: revision.DeployUser,
Note: revision.Note,
TriggerType: revision.TriggerType,
CreateTime: revision.CreateTime,
EnvName: revision.EnvName,
CodeInfo: revision.CodeInfo,
}
}
// DeleteApplication delete application
func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *model.Application) error {
// TODO: check app can be deleted
@@ -897,6 +935,11 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod
return err
}
triggers, err := c.ListApplicationTriggers(ctx, app)
if err != nil {
return err
}
// delete workflow
if err := c.workflowUsecase.DeleteWorkflowByApp(ctx, app); err != nil && !errors.Is(err, bcode.ErrWorkflowNotExist) {
log.Logger.Errorf("delete workflow %s failure %s", app.Name, err.Error())
@@ -923,6 +966,12 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod
}
}
for _, trigger := range triggers {
if err := c.ds.Delete(ctx, &model.ApplicationTrigger{AppPrimaryKey: app.PrimaryKey(), Name: trigger.Name, Token: trigger.Token}); err != nil {
log.Logger.Errorf("delete trigger %s in app %s failure %s", trigger.Name, app.Name, err.Error())
}
}
if err := c.envBindingUsecase.BatchDeleteEnvBinding(ctx, app); err != nil {
log.Logger.Errorf("delete envbindings in app %s failure %s", app.Name, err.Error())
}
@@ -1127,7 +1176,7 @@ func (c *applicationUsecaseImpl) UpdatePolicy(ctx context.Context, app *model.Ap
return nil, err
}
return &apisv1.DetailPolicyResponse{
PolicyBase: *c.converPolicyModelToBase(&policy),
PolicyBase: *convertPolicyModelToBase(&policy),
}, nil
}
@@ -1230,16 +1279,7 @@ func (c *applicationUsecaseImpl) ListRevisions(ctx context.Context, appName, env
for _, raw := range revisions {
r, ok := raw.(*model.ApplicationRevision)
if ok {
resp.Revisions = append(resp.Revisions, apisv1.ApplicationRevisionBase{
CreateTime: r.CreateTime,
Version: r.Version,
Status: r.Status,
Reason: r.Reason,
DeployUser: r.DeployUser,
Note: r.Note,
EnvName: r.EnvName,
TriggerType: r.TriggerType,
})
resp.Revisions = append(resp.Revisions, c.converRevisionModelToBase(r))
}
}
count, err := c.ds.Count(ctx, &revision, nil)
@@ -1280,21 +1320,15 @@ func (c *applicationUsecaseImpl) Statistics(ctx context.Context, app *model.Appl
return nil, err
}
return &apisv1.ApplicationStatisticsResponse{
EnvCount: int64(len(envbinding)),
DeliveryTargetCount: int64(len(targetMap)),
RevisonCount: count,
WorkflowCount: c.workflowUsecase.CountWorkflow(ctx, app),
EnvCount: int64(len(envbinding)),
TargetCount: int64(len(targetMap)),
RevisonCount: count,
WorkflowCount: c.workflowUsecase.CountWorkflow(ctx, app),
}, nil
}
func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, app *model.Application, envBind *model.EnvBinding, target *model.DeliveryTarget, components []*model.ApplicationComponent) v1alpha1.EnvConfig {
func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, envBind *model.EnvBinding, env *model.Env, target *model.Target, components []*model.ApplicationComponent) v1alpha1.EnvConfig {
placement := v1alpha1.EnvPlacement{}
var componentSelector *v1alpha1.EnvSelector
if envBind.ComponentSelector != nil {
componentSelector = &v1alpha1.EnvSelector{
Components: envBind.ComponentSelector.Components,
}
}
if target.Cluster != nil {
placement.ClusterSelector = &common.ClusterSelector{Name: target.Cluster.ClusterName}
placement.NamespaceSelector = &v1alpha1.NamespaceSelector{Name: target.Cluster.Namespace}
@@ -1302,7 +1336,7 @@ func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, app
var componentPatchs []v1alpha1.EnvComponentPatch
// init cloud application region and provider info
for _, component := range components {
definition, err := c.definitionUsecase.GetComponentDefinition(ctx, component.Type)
definition, err := GetComponentDefinition(ctx, c.kubeClient, component.Type)
if err != nil {
log.Logger.Errorf("get component definition %s failure %s", component.Type, err.Error())
continue
@@ -1316,7 +1350,7 @@ func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, app
},
"writeConnectionSecretToRef": map[string]interface{}{
"name": fmt.Sprintf("%s-%s", component.Name, envBind.Name),
"namespace": app.Namespace,
"namespace": env.Namespace,
},
}
if region, ok := target.Variable["region"]; ok {
@@ -1329,7 +1363,7 @@ func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, app
properties["providerRef"].(map[string]interface{})["namespace"] = providerNamespace
}
componentPatchs = append(componentPatchs, v1alpha1.EnvComponentPatch{
Name: converComponentName(component.Name, envBind.Name),
Name: component.Name,
Properties: properties.RawExtension(),
Type: component.Type,
})
@@ -1340,21 +1374,12 @@ func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, app
return v1alpha1.EnvConfig{
Name: genPolicyEnvName(target.Name),
Placement: placement,
Selector: componentSelector,
Patch: v1alpha1.EnvPatch{
Components: componentPatchs,
},
}
}
func convertAppName(appModelName, envName string) string {
return fmt.Sprintf("%s-%s", appModelName, envName)
}
func converComponentName(componentModelName, envName string) string {
return fmt.Sprintf("%s-%s", componentModelName, envName)
}
func genPolicyName(envName string) string {
return fmt.Sprintf("%s-%s", EnvBindingPolicyDefaultName, envName)
}
@@ -1362,3 +1387,14 @@ func genPolicyName(envName string) string {
func genPolicyEnvName(targetName string) string {
return targetName
}
func genWebhookToken() string {
rand.Seed(time.Now().UnixNano())
runes := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
b := make([]rune, defaultTokenLen)
for i := range b {
b[i] = runes[rand.Intn(len(runes))] // #nosec
}
return string(b)
}

View File

@@ -19,8 +19,8 @@ package usecase
import (
"context"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
@@ -36,6 +36,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"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/bcode"
@@ -45,230 +46,164 @@ import (
var _ = Describe("Test application usecase function", func() {
var (
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
envBindingUsecase *envBindingUsecaseImpl
deliveryTargetUsecase *deliveryTargetUsecaseImpl
definitionUsecase *definitionUsecaseImpl
projectUsecase *projectUsecaseImpl
testProject = "app-project"
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
envUsecase *envUsecaseImpl
envBindingUsecase *envBindingUsecaseImpl
targetUsecase *targetUsecaseImpl
definitionUsecase *definitionUsecaseImpl
projectUsecase *projectUsecaseImpl
testProject = "app-project"
testApp = "test-app"
defaultTarget = "default"
namespace1 = "app-test1"
envnsdev = "envnsdev"
envnstest = "envnstest"
)
BeforeEach(func() {
workflowUsecase = &workflowUsecaseImpl{ds: ds}
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "app-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
deliveryTargetUsecase = &deliveryTargetUsecaseImpl{ds: ds}
projectUsecase = &projectUsecaseImpl{ds: ds, kubeClient: k8sClient}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
appUsecase = &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
apply: apply.NewAPIApplicator(k8sClient),
kubeClient: k8sClient,
envBindingUsecase: envBindingUsecase,
definitionUsecase: definitionUsecase,
deliveryTargetUsecase: deliveryTargetUsecase,
projectUsecase: projectUsecase,
ds: ds,
workflowUsecase: workflowUsecase,
apply: apply.NewAPIApplicator(k8sClient),
kubeClient: k8sClient,
envBindingUsecase: envBindingUsecase,
envUsecase: envUsecase,
definitionUsecase: definitionUsecase,
targetUsecase: targetUsecase,
projectUsecase: projectUsecase,
}
})
It("Test CreateApplication function", func() {
By("test sample create")
_, err := projectUsecase.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: testProject})
_, err := targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: defaultTarget, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: namespace1}})
Expect(err).Should(BeNil())
_, err = projectUsecase.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: testProject})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), v1.CreateEnvRequest{Name: "app-dev", Namespace: envnsdev, Targets: []string{defaultTarget}, Project: "app-dev"})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), v1.CreateEnvRequest{Name: "app-test", Namespace: envnstest, Targets: []string{defaultTarget}, Project: "app-test"})
Expect(err).Should(BeNil())
req := v1.CreateApplicationRequest{
Name: "test-app",
Name: testApp,
Project: testProject,
Description: "this is a test app",
EnvBinding: []*v1.EnvBinding{{
Name: "dev",
Description: "dev env",
TargetNames: []string{"dev-target"},
Name: "app-dev",
}, {
Name: "test",
Description: "test env",
TargetNames: []string{"test-target"},
Name: "app-test",
}},
Component: &v1.CreateComponentRequest{
Name: "component-name",
ComponentType: "webservice",
Properties: "{\"image\":\"nginx\"}",
},
}
base, err := appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
detail, err := appUsecase.DetailComponent(context.TODO(), &model.Application{Name: "test-app", Project: testProject}, "component-name")
triggers, err := appUsecase.ListApplicationTriggers(context.TODO(), &model.Application{Name: testApp})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(detail.Traits), 1)).Should(BeEmpty())
_, err = appUsecase.CreateApplication(context.TODO(), req)
equal := cmp.Equal(err, bcode.ErrApplicationExist, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
By("test with oam yaml config create")
bs, err := ioutil.ReadFile("./testdata/example-app.yaml")
Expect(err).Should(Succeed())
req = v1.CreateApplicationRequest{
Name: "test-app-sadasd",
Project: testProject,
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
YamlConfig: string(bs),
}
base, err = appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
req = v1.CreateApplicationRequest{
Name: "test-app-sadasd2",
Project: testProject,
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
YamlConfig: "asdasdasdasd",
}
base, err = appUsecase.CreateApplication(context.TODO(), req)
equal = cmp.Equal(err, bcode.ErrApplicationConfig, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
Expect(base).Should(BeNil())
bs, err = ioutil.ReadFile("./testdata/example-app-error.yaml")
Expect(err).Should(Succeed())
req = v1.CreateApplicationRequest{
Name: "test-app-sadasd3",
Project: testProject,
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
YamlConfig: string(bs),
}
_, err = appUsecase.CreateApplication(context.TODO(), req)
equal = cmp.Equal(err, bcode.ErrInvalidProperties, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
By("Test create app with env binding")
req = v1.CreateApplicationRequest{
Name: "test-app-sadasd4",
Project: testProject,
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
EnvBinding: []*v1.EnvBinding{
{
Name: "dev",
Alias: "Chinese Word",
Description: "This is a dev env",
TargetNames: []string{"dev-target"},
},
{
Name: "prod",
Description: "This is a prod env",
TargetNames: []string{"prod-target"},
},
},
}
appBase, err := appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appBase.Name, "test-app-sadasd4")).Should(BeEmpty())
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd4")
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
Expect(len(triggers)).Should(Equal(1))
})
It("Test ListApplications function", func() {
_, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{})
_, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicationOptions{})
Expect(err).Should(BeNil())
})
It("Test ListApplications and filter by targetName function", func() {
list, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{
list, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicationOptions{
Project: testProject,
TargetName: "dev-target"})
TargetName: defaultTarget})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(list), 2)).Should(BeEmpty())
Expect(cmp.Diff(len(list), 1)).Should(BeEmpty())
})
It("Test DetailApplication function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
detail, err := appUsecase.DetailApplication(context.TODO(), appModel)
Expect(err).Should(BeNil())
Expect(cmp.Diff(detail.ResourceInfo.ComponentNum, int64(2))).Should(BeEmpty())
Expect(cmp.Diff(detail.ResourceInfo.ComponentNum, int64(1))).Should(BeEmpty())
Expect(cmp.Diff(len(detail.Policies), 0)).Should(BeEmpty())
})
It("Test CreateTrigger function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.CreateApplicationTrigger(context.TODO(), appModel, v1.CreateApplicationTriggerRequest{
Name: "trigger-name",
})
Expect(err).Should(BeNil())
})
It("Test ListTriggers function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
triggers, err := appUsecase.ListApplicationTriggers(context.TODO(), appModel)
Expect(err).Should(BeNil())
Expect(len(triggers)).Should(Equal(2))
})
It("Test ListComponents function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
components, err := appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 2)).Should(BeEmpty())
Expect(cmp.Diff(components[0].ComponentType, "worker")).Should(BeEmpty())
Expect(components[1].UpdateTime).ShouldNot(BeNil())
components, err = appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{
EnvName: "test",
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 2)).Should(BeEmpty())
Expect(cmp.Diff(components[0].Name, "data-worker")).Should(BeEmpty())
components, err = appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{
EnvName: "staging",
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 2)).Should(BeEmpty())
Expect(cmp.Diff(components[0].Name, "data-worker")).Should(BeEmpty())
})
It("Test DetailComponent function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
detail, err := appUsecase.DetailComponent(context.TODO(), appModel, "hello-world-server")
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(detail.Traits), 1)).Should(BeEmpty())
Expect(cmp.Diff(detail.Type, "webservice")).Should(BeEmpty())
Expect(cmp.Diff(strings.Contains((*detail.Properties)["image"].(string), "crccheck/hello-world"), true)).Should(BeEmpty())
Expect(cmp.Diff(len(components), 1)).Should(BeEmpty())
Expect(cmp.Diff(components[0].ComponentType, "webservice")).Should(BeEmpty())
})
It("Test AddComponent function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
base, err := appUsecase.AddComponent(context.TODO(), appModel, v1.CreateComponentRequest{
Name: "test2",
Description: "this is a test2 component",
Labels: map[string]string{},
ComponentType: "worker",
Properties: `{"image": "busybox","cmd":["sleep", "1000"],"lives": "3","enemies": "alien"}`,
DependsOn: []string{"data-worker"},
DependsOn: []string{"component-name"},
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.ComponentType, "worker")).Should(BeEmpty())
})
It("Test DetailComponent function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
detailResponse, err := appUsecase.DetailComponent(context.TODO(), appModel, "test2")
Expect(err).Should(BeNil())
Expect(cmp.Diff(detailResponse.DependsOn[0], "data-worker")).Should(BeEmpty())
Expect(cmp.Diff(detailResponse.DependsOn[0], "component-name")).Should(BeEmpty())
Expect(detailResponse.Properties).ShouldNot(BeNil())
Expect(cmp.Diff((*detailResponse.Properties)["image"], "busybox")).Should(BeEmpty())
})
It("Test AddPolicy function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
_, err = appUsecase.AddPolicy(context.TODO(), appModel, v1.CreatePolicyRequest{
@@ -289,7 +224,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test ListPolicies function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
@@ -299,7 +234,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test DetailPolicy function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
detail, err := appUsecase.DetailPolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName)
@@ -309,7 +244,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test UpdatePolicy function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
base, err := appUsecase.UpdatePolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName, v1.UpdatePolicyRequest{
@@ -321,7 +256,7 @@ var _ = Describe("Test application usecase function", func() {
Expect((*base.Properties)["envs"]).Should(BeEmpty())
})
It("Test DeletePolicy function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
err = appUsecase.DeletePolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName)
@@ -329,7 +264,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test add application trait", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
alias := "alias"
description := "description"
@@ -351,7 +286,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test add application a dup trait", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.CreateApplicationTrait(context.TODO(), appModel, &model.ApplicationComponent{Name: "test2"}, v1.CreateApplicationTraitRequest{
Type: "Ingress",
@@ -361,7 +296,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test update application trait", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
alias := "newAlias"
description := "newDescription"
@@ -382,7 +317,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test update a not exist", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.UpdateApplicationTrait(context.TODO(), appModel, &model.ApplicationComponent{Name: "test2"}, "Ingress-1-20", v1.UpdateApplicationTraitRequest{
Properties: `{"domain":"www.test1.com"}`,
@@ -391,7 +326,7 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test delete an exist trait", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
err = appUsecase.DeleteApplicationTrait(context.TODO(), appModel, &model.ApplicationComponent{Name: "test2"}, "Ingress")
Expect(err).Should(BeNil())
@@ -402,30 +337,17 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test DeleteComponent function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Project, testProject)).Should(BeEmpty())
err = appUsecase.DeleteComponent(context.TODO(), appModel, "test2")
Expect(err).Should(BeNil())
})
It("Test DeleteApplication function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
err = appUsecase.DeleteApplication(context.TODO(), appModel)
Expect(err).Should(BeNil())
components, err := appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 0)).Should(BeEmpty())
policies, err := appUsecase.ListPolicies(context.TODO(), appModel)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(policies), 0)).Should(BeEmpty())
})
It("Test ListRevisions function", func() {
for i := 0; i < 3; i++ {
appModel := &model.ApplicationRevision{
AppPrimaryKey: "test-app",
AppPrimaryKey: "test-app-sadasd",
Version: fmt.Sprintf("%d", i),
EnvName: fmt.Sprintf("env-%d", i),
Status: model.RevisionStatusRunning,
@@ -436,15 +358,15 @@ var _ = Describe("Test application usecase function", func() {
err := workflowUsecase.createTestApplicationRevision(context.TODO(), appModel)
Expect(err).Should(BeNil())
}
revisions, err := appUsecase.ListRevisions(context.TODO(), "test-app", "", "", 0, 10)
revisions, err := appUsecase.ListRevisions(context.TODO(), "test-app-sadasd", "", "", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(3)))
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app", "env-0", "", 0, 10)
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app-sadasd", "env-0", "", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(1)))
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app", "", "terminated", 0, 10)
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app-sadasd", "", "terminated", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(1)))
@@ -467,19 +389,13 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test ApplicationEnvRecycle function", func() {
req := v1.CreateApplicationRequest{
Name: "app-env-recycle" + "-dev",
Project: testProject,
Description: "this is a test app with env",
}
base, err := appUsecase.CreateApplication(context.TODO(), req)
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.Deploy(context.TODO(), appModel, v1.ApplicationDeployRequest{WorkflowName: convertWorkflowName("app-dev")})
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
err = envBindingUsecase.ApplicationEnvRecycle(context.TODO(), &model.Application{
Name: "app-env-recycle",
Namespace: "project-" + testProject,
}, &model.EnvBinding{Name: "dev"})
Name: testApp,
}, &model.EnvBinding{Name: "app-dev"})
Expect(err).Should(BeNil())
})
@@ -540,11 +456,11 @@ var _ = Describe("Test application usecase function", func() {
}
err = k8sClient.Create(context.TODO(), definition)
Expect(err).Should(BeNil())
envConfig := appUsecase.createTargetClusterEnv(context.TODO(), &model.Application{
Namespace: "prod",
}, &model.EnvBinding{
TargetNames: []string{"prod"},
}, &model.DeliveryTarget{
envConfig := appUsecase.createTargetClusterEnv(context.TODO(), &model.EnvBinding{
Name: "prod",
}, &model.Env{
Name: "prod",
}, &model.Target{
Name: "prod",
Variable: map[string]interface{}{
"region": "hangzhou",
@@ -561,13 +477,27 @@ var _ = Describe("Test application usecase function", func() {
err = k8sClient.Delete(context.TODO(), definition)
Expect(err).Should(BeNil())
})
It("Test DeleteApplication function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
time.Sleep(time.Second * 3)
err = appUsecase.DeleteApplication(context.TODO(), appModel)
Expect(err).Should(BeNil())
components, err := appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 0)).Should(BeEmpty())
policies, err := appUsecase.ListPolicies(context.TODO(), appModel)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(policies), 0)).Should(BeEmpty())
})
})
func createTestSuspendApp(ctx context.Context, appName, envName, revisionVersion, wfName, recordName string, kubeClient client.Client) (*v1beta1.Application, error) {
testapp := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: convertAppName(appName, envName),
Namespace: "default",
Name: appName,
Namespace: envName,
Annotations: map[string]string{
oam.AnnotationDeployVersion: revisionVersion,
oam.AnnotationWorkflowName: wfName,
@@ -578,8 +508,8 @@ func createTestSuspendApp(ctx context.Context, appName, envName, revisionVersion
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "test-component",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte(`{"test":"test"}`)},
Type: "webservice",
Properties: &runtime.RawExtension{Raw: []byte(`{"image":"nginx"}`)},
Traits: []common.ApplicationTrait{},
Scopes: map[string]string{},
}},

View File

@@ -162,7 +162,7 @@ func (c *clusterUsecaseImpl) ListKubeClusters(ctx context.Context, query string,
clusters, err := c.ds.List(ctx, &model.Cluster{}, &datastore.ListOptions{
Page: page,
PageSize: pageSize,
SortBy: []datastore.SortOption{{Key: "model.createTime", Order: datastore.SortOrderDescending}},
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
FilterOptions: fo,
})
if err != nil {
@@ -174,7 +174,12 @@ func (c *clusterUsecaseImpl) ListKubeClusters(ctx context.Context, query string,
for _, raw := range clusters {
cluster, ok := raw.(*model.Cluster)
if ok {
resp.Clusters = append(resp.Clusters, *newClusterBaseFromCluster(cluster))
// local cluster must be first
if cluster.Name == multicluster.ClusterLocalName {
resp.Clusters = append([]apis.ClusterBase{*newClusterBaseFromCluster(cluster)}, resp.Clusters...)
} else {
resp.Clusters = append(resp.Clusters, *newClusterBaseFromCluster(cluster))
}
}
}
total, err := c.ds.Count(ctx, &model.Cluster{}, &fo)

View File

@@ -0,0 +1,77 @@
/*
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 usecase
import (
"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/bcode"
)
func convertPolicyModelToBase(policy *model.ApplicationPolicy) *apisv1.PolicyBase {
pb := &apisv1.PolicyBase{
Name: policy.Name,
Type: policy.Type,
Properties: policy.Properties,
Description: policy.Description,
Creator: policy.Creator,
CreateTime: policy.CreateTime,
UpdateTime: policy.UpdateTime,
}
return pb
}
func convertWorkflowBase(workflow *model.Workflow) apisv1.WorkflowBase {
var steps []apisv1.WorkflowStep
for _, step := range workflow.Steps {
steps = append(steps, convertFromWorkflowStepModel(step))
}
return apisv1.WorkflowBase{
Name: workflow.Name,
Alias: workflow.Alias,
Description: workflow.Description,
Default: convertBool(workflow.Default),
EnvName: workflow.EnvName,
CreateTime: workflow.CreateTime,
UpdateTime: workflow.UpdateTime,
Steps: steps,
}
}
// convertAPIStep2ModelStep will convert api types of workflow step to model type
func convertAPIStep2ModelStep(apiSteps []apisv1.WorkflowStep) ([]model.WorkflowStep, error) {
var steps []model.WorkflowStep
for _, step := range apiSteps {
properties, err := model.NewJSONStructByString(step.Properties)
if err != nil {
log.Logger.Errorf("parse trait properties failire %w", err)
return nil, bcode.ErrInvalidProperties
}
steps = append(steps, model.WorkflowStep{
Name: step.Name,
Alias: step.Alias,
Description: step.Description,
DependsOn: step.DependsOn,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
Properties: properties,
})
}
return steps, nil
}

View File

@@ -34,7 +34,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"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"
@@ -52,8 +51,6 @@ type DefinitionUsecase interface {
DetailDefinition(ctx context.Context, name, defType string) (*apisv1.DetailDefinitionResponse, error)
// AddDefinitionUISchema add or update custom definition ui schema
AddDefinitionUISchema(ctx context.Context, name, defType, configRaw string) ([]*utils.UIParameter, error)
// GetComponentDefinition get component definition
GetComponentDefinition(ctx context.Context, name string) (*v1alpha2.ComponentDefinition, error)
}
type definitionUsecaseImpl struct {
@@ -162,14 +159,6 @@ func (d *definitionUsecaseImpl) listDefinitions(ctx context.Context, list *unstr
return defs, nil
}
func (d *definitionUsecaseImpl) GetComponentDefinition(ctx context.Context, name string) (*v1alpha2.ComponentDefinition, error) {
var componentDefinition v1alpha2.ComponentDefinition
if err := d.kubeClient.Get(ctx, k8stypes.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: name}, &componentDefinition); err != nil {
return nil, err
}
return &componentDefinition, nil
}
// DetailDefinition get definition detail
func (d *definitionUsecaseImpl) DetailDefinition(ctx context.Context, name, defType string) (*apisv1.DetailDefinitionResponse, error) {
if !utils.StringsContain([]string{"component", "trait", "workflowstep"}, defType) {
@@ -274,6 +263,9 @@ func patchSchema(defaultSchema, customSchema []*utils.UIParameter) []*utils.UIPa
for i, custom := range customSchema {
customSchemaMap[custom.JSONKey] = customSchema[i]
}
if len(defaultSchema) == 0 {
return customSchema
}
for i := range defaultSchema {
dSchema := defaultSchema[i]
if cusSchema, exist := customSchemaMap[dSchema.JSONKey]; exist {

View File

@@ -1,199 +0,0 @@
/*
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 usecase
import (
"context"
"errors"
"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/bcode"
)
// DeliveryTargetUsecase deliveryTarget manage api
type DeliveryTargetUsecase interface {
GetDeliveryTarget(ctx context.Context, deliveryTargetName string) (*model.DeliveryTarget, error)
DetailDeliveryTarget(ctx context.Context, deliveryTarget *model.DeliveryTarget) (*apisv1.DetailDeliveryTargetResponse, error)
DeleteDeliveryTarget(ctx context.Context, deliveryTargetName string) error
CreateDeliveryTarget(ctx context.Context, req apisv1.CreateDeliveryTargetRequest) (*apisv1.DetailDeliveryTargetResponse, error)
UpdateDeliveryTarget(ctx context.Context, deliveryTarget *model.DeliveryTarget, req apisv1.UpdateDeliveryTargetRequest) (*apisv1.DetailDeliveryTargetResponse, error)
ListDeliveryTargets(ctx context.Context, page, pageSize int, project string) (*apisv1.ListTargetResponse, error)
}
type deliveryTargetUsecaseImpl struct {
ds datastore.DataStore
projectUsecase ProjectUsecase
}
// NewDeliveryTargetUsecase new DeliveryTarget usecase
func NewDeliveryTargetUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase) DeliveryTargetUsecase {
return &deliveryTargetUsecaseImpl{
ds: ds,
projectUsecase: projectUsecase,
}
}
func (dt *deliveryTargetUsecaseImpl) ListDeliveryTargets(ctx context.Context, page, pageSize int, project string) (*apisv1.ListTargetResponse, error) {
deliveryTarget := model.DeliveryTarget{}
if project != "" {
deliveryTarget.Project = project
}
deliveryTargets, err := dt.ds.List(ctx, &deliveryTarget, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
resp := &apisv1.ListTargetResponse{
Targets: []apisv1.DeliveryTargetBase{},
}
for _, raw := range deliveryTargets {
target, ok := raw.(*model.DeliveryTarget)
if ok {
resp.Targets = append(resp.Targets, *(dt.convertFromDeliveryTargetModel(ctx, target)))
}
}
count, err := dt.ds.Count(ctx, &deliveryTarget, nil)
if err != nil {
return nil, err
}
resp.Total = count
return resp, nil
}
// DeleteDeliveryTarget delete application DeliveryTarget
func (dt *deliveryTargetUsecaseImpl) DeleteDeliveryTarget(ctx context.Context, deliveryTargetName string) error {
deliveryTarget := &model.DeliveryTarget{
Name: deliveryTargetName,
}
if err := dt.ds.Delete(ctx, deliveryTarget); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrDeliveryTargetNotExist
}
return err
}
return nil
}
func (dt *deliveryTargetUsecaseImpl) CreateDeliveryTarget(ctx context.Context, req apisv1.CreateDeliveryTargetRequest) (*apisv1.DetailDeliveryTargetResponse, error) {
deliveryTarget := convertCreateReqToDeliveryTargetModel(req)
// check deliveryTarget name.
exit, err := dt.ds.IsExist(ctx, &deliveryTarget)
if err != nil {
log.Logger.Errorf("check application name is exist failure %s", err.Error())
return nil, bcode.ErrDeliveryTargetExist
}
if exit {
return nil, bcode.ErrDeliveryTargetExist
}
// check project
project, err := dt.projectUsecase.GetProject(ctx, req.Project)
if err != nil {
return nil, err
}
deliveryTarget.Namespace = project.Namespace
deliveryTarget.Project = project.Name
if err := dt.ds.Add(ctx, &deliveryTarget); err != nil {
return nil, err
}
return dt.DetailDeliveryTarget(ctx, &deliveryTarget)
}
func (dt *deliveryTargetUsecaseImpl) UpdateDeliveryTarget(ctx context.Context, deliveryTarget *model.DeliveryTarget, req apisv1.UpdateDeliveryTargetRequest) (*apisv1.DetailDeliveryTargetResponse, error) {
deliveryTargetModel := convertUpdateReqToDeliveryTargetModel(deliveryTarget, req)
if err := dt.ds.Put(ctx, deliveryTargetModel); err != nil {
return nil, err
}
return dt.DetailDeliveryTarget(ctx, deliveryTargetModel)
}
// DetailDeliveryTarget detail DeliveryTarget
func (dt *deliveryTargetUsecaseImpl) DetailDeliveryTarget(ctx context.Context, deliveryTarget *model.DeliveryTarget) (*apisv1.DetailDeliveryTargetResponse, error) {
return &apisv1.DetailDeliveryTargetResponse{
DeliveryTargetBase: *dt.convertFromDeliveryTargetModel(ctx, deliveryTarget),
}, nil
}
// GetDeliveryTarget get DeliveryTarget model
func (dt *deliveryTargetUsecaseImpl) GetDeliveryTarget(ctx context.Context, deliveryTargetName string) (*model.DeliveryTarget, error) {
deliveryTarget := &model.DeliveryTarget{
Name: deliveryTargetName,
}
if err := dt.ds.Get(ctx, deliveryTarget); err != nil {
return nil, err
}
return deliveryTarget, nil
}
func convertUpdateReqToDeliveryTargetModel(deliveryTarget *model.DeliveryTarget, req apisv1.UpdateDeliveryTargetRequest) *model.DeliveryTarget {
deliveryTarget.Alias = req.Alias
deliveryTarget.Description = req.Description
deliveryTarget.Cluster = (*model.ClusterTarget)(req.Cluster)
deliveryTarget.Variable = req.Variable
return deliveryTarget
}
func convertCreateReqToDeliveryTargetModel(req apisv1.CreateDeliveryTargetRequest) model.DeliveryTarget {
deliveryTarget := model.DeliveryTarget{
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Cluster: (*model.ClusterTarget)(req.Cluster),
Variable: req.Variable,
}
return deliveryTarget
}
func (dt *deliveryTargetUsecaseImpl) convertFromDeliveryTargetModel(ctx context.Context, deliveryTarget *model.DeliveryTarget) *apisv1.DeliveryTargetBase {
var appNum int64 = 0
// TODO: query app num in target
targetBase := &apisv1.DeliveryTargetBase{
Name: deliveryTarget.Name,
Alias: deliveryTarget.Alias,
Description: deliveryTarget.Description,
Cluster: (*apisv1.ClusterTarget)(deliveryTarget.Cluster),
Variable: deliveryTarget.Variable,
CreateTime: deliveryTarget.CreateTime,
UpdateTime: deliveryTarget.UpdateTime,
AppNum: appNum,
}
project, err := dt.projectUsecase.GetProject(ctx, deliveryTarget.Project)
if err != nil {
log.Logger.Errorf("query project info failure %s", err.Error())
}
if project != nil {
targetBase.Project = convertProjectModel2Base(project)
}
if targetBase.Cluster != nil && targetBase.Cluster.ClusterName != "" {
cluster, err := _getClusterFromDataStore(ctx, dt.ds, deliveryTarget.Cluster.ClusterName)
if err != nil {
log.Logger.Errorf("query cluster info failure %s", err.Error())
}
if cluster != nil {
targetBase.ClusterAlias = cluster.Alias
}
}
return targetBase
}

View File

@@ -0,0 +1,301 @@
/*
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 usecase
import (
"context"
"errors"
"reflect"
"sort"
apierror "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
util "github.com/oam-dev/kubevela/pkg/utils"
)
// EnvUsecase defines the API of Env.
type EnvUsecase interface {
GetEnv(ctx context.Context, envName string) (*model.Env, error)
ListEnvs(ctx context.Context, page, pageSize int, listOption apisv1.ListEnvOptions) ([]*apisv1.Env, error)
DeleteEnv(ctx context.Context, envName string) error
CreateEnv(ctx context.Context, req apisv1.CreateEnvRequest) (*apisv1.Env, error)
UpdateEnv(ctx context.Context, envName string, req apisv1.UpdateEnvRequest) (*apisv1.Env, error)
}
type envUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
}
// NewEnvUsecase new env usecase
func NewEnvUsecase(ds datastore.DataStore) EnvUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &envUsecaseImpl{kubeClient: kubecli, ds: ds}
}
// GetEnv get env
func (p *envUsecaseImpl) GetEnv(ctx context.Context, envName string) (*model.Env, error) {
return getEnv(ctx, p.ds, envName)
}
// DeleteEnv delete an env by name
// the function assume applications contain in env already empty.
// it won't delete the namespace created by the Env, but it will update the label
func (p *envUsecaseImpl) DeleteEnv(ctx context.Context, envName string) error {
env := &model.Env{}
env.Name = envName
if err := p.ds.Get(ctx, env); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil
}
return err
}
// reset the labels
err := util.UpdateNamespace(ctx, p.kubeClient, env.Namespace, util.MergeOverrideLabels(map[string]string{
oam.LabelNamespaceOfEnvName: "",
oam.LabelControlPlaneNamespaceUsage: "",
}))
if err != nil && apierror.IsNotFound(err) {
return err
}
if err = p.ds.Delete(ctx, env); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil
}
return err
}
return nil
}
// ListEnvs list envs
func (p *envUsecaseImpl) ListEnvs(ctx context.Context, page, pageSize int, listOption apisv1.ListEnvOptions) ([]*apisv1.Env, error) {
entities, err := listEnvs(ctx, p.ds, listOption.Project, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
Targets, err := listTarget(ctx, p.ds, nil)
if err != nil {
return nil, err
}
var envs []*apisv1.Env
for _, ee := range entities {
envs = append(envs, convertEnvModel2Base(ee, Targets))
}
projects, err := listProjects(ctx, p.ds)
if err != nil {
return nil, err
}
for _, e := range envs {
for _, pj := range projects {
if e.Project.Name == pj.Name {
e.Project.Alias = pj.Alias
break
}
}
}
return envs, nil
}
func checkEqual(old, new []string) bool {
if old == nil && new == nil {
return true
}
if old == nil || new == nil {
return false
}
sort.Strings(old)
sort.Strings(new)
return reflect.DeepEqual(old, new)
}
func (p *envUsecaseImpl) updateAppWithNewEnv(ctx context.Context, envName string, env *model.Env) error {
// List all apps inside the env
apps, err := listApp(ctx, p.ds, apisv1.ListApplicationOptions{Env: envName})
if err != nil {
return err
}
for _, app := range apps {
err = UpdateEnvWorkflow(ctx, p.kubeClient, p.ds, app, env)
if err != nil {
return err
}
}
return nil
}
// UpdateEnv update an env for request
func (p *envUsecaseImpl) UpdateEnv(ctx context.Context, name string, req apisv1.UpdateEnvRequest) (*apisv1.Env, error) {
env := &model.Env{}
env.Name = name
err := p.ds.Get(ctx, env)
if err != nil {
log.Logger.Errorf("check if env name exists failure %s", err.Error())
return nil, bcode.ErrEnvNotExisted
}
if req.Alias != "" {
env.Alias = req.Alias
}
if req.Description != "" {
env.Description = req.Description
}
pass, err := p.checkEnvTarget(ctx, env.Project, env.Name, req.Targets)
if err != nil || !pass {
return nil, bcode.ErrEnvTargetConflict
}
var targetChanged bool
if len(req.Targets) > 0 && !checkEqual(env.Targets, req.Targets) {
targetChanged = true
env.Targets = req.Targets
}
targets, err := listTarget(ctx, p.ds, nil)
if err != nil {
return nil, err
}
var targetMap = make(map[string]*model.Target, len(targets))
for i, existTarget := range targets {
targetMap[existTarget.Name] = targets[i]
}
for _, target := range req.Targets {
if _, exist := targetMap[target]; !exist {
return nil, bcode.ErrTargetNotExist
}
}
// create namespace at first
if err := p.ds.Put(ctx, env); err != nil {
return nil, err
}
if targetChanged {
if err = p.updateAppWithNewEnv(ctx, name, env); err != nil {
log.Logger.Errorf("update envbinding failure %s", err.Error())
return nil, err
}
}
resp := convertEnvModel2Base(env, targets)
return resp, nil
}
// CreateEnv create an env for request
func (p *envUsecaseImpl) CreateEnv(ctx context.Context, req apisv1.CreateEnvRequest) (*apisv1.Env, error) {
newEnv := &model.Env{
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Namespace: req.Namespace,
Project: req.Project,
Targets: req.Targets,
}
pass, err := p.checkEnvTarget(ctx, req.Project, req.Name, req.Targets)
if err != nil || !pass {
return nil, bcode.ErrEnvTargetConflict
}
targets, err := listTarget(ctx, p.ds, nil)
if err != nil {
return nil, err
}
var targetMap = make(map[string]*model.Target, len(targets))
for i, existTarget := range targets {
targetMap[existTarget.Name] = targets[i]
}
for _, target := range req.Targets {
if _, exist := targetMap[target]; !exist {
return nil, bcode.ErrTargetNotExist
}
}
err = createEnv(ctx, p.kubeClient, p.ds, newEnv)
if err != nil {
return nil, err
}
resp := convertEnvModel2Base(newEnv, targets)
return resp, nil
}
// checkEnvTarget In one project, a delivery target can only belong to one env.
func (p *envUsecaseImpl) checkEnvTarget(ctx context.Context, project string, envName string, targets []string) (bool, error) {
if len(targets) == 0 {
return true, nil
}
entitys, err := p.ds.List(ctx, &model.Env{Project: project}, &datastore.ListOptions{})
if err != nil {
return false, err
}
newMap := make(map[string]bool, len(targets))
for _, new := range targets {
newMap[new] = true
}
for _, entity := range entitys {
env := entity.(*model.Env)
for _, existTarget := range env.Targets {
if ok := newMap[existTarget]; ok && env.Name != envName {
return false, nil
}
}
}
return true, nil
}
func convertEnvModel2Base(env *model.Env, targets []*model.Target) *apisv1.Env {
data := apisv1.Env{
Name: env.Name,
Alias: env.Alias,
Description: env.Description,
Project: apisv1.NameAlias{Name: env.Project},
Namespace: env.Namespace,
CreateTime: env.CreateTime,
UpdateTime: env.UpdateTime,
}
for _, dt := range env.Targets {
for _, tg := range targets {
if dt == tg.Name {
data.Targets = append(data.Targets, apisv1.NameAlias{
Name: dt,
Alias: tg.Alias,
})
}
}
}
return &data
}

View File

@@ -0,0 +1,101 @@
/*
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 usecase
import (
"context"
"errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
util "github.com/oam-dev/kubevela/pkg/utils"
velaerr "github.com/oam-dev/kubevela/pkg/utils/errors"
)
func createEnv(ctx context.Context, kubeClient client.Client, ds datastore.DataStore, env *model.Env) error {
tenv := &model.Env{}
tenv.Name = env.Name
exist, err := ds.IsExist(ctx, tenv)
if err != nil {
log.Logger.Errorf("check if env name exists failure %s", err.Error())
return err
}
if exist {
return bcode.ErrEnvAlreadyExists
}
if env.Namespace == "" {
env.Namespace = env.Name
}
// create namespace at first
err = util.CreateOrUpdateNamespace(ctx, kubeClient, env.Namespace,
util.MergeOverrideLabels(map[string]string{
oam.LabelControlPlaneNamespaceUsage: oam.VelaNamespaceUsageEnv,
}), util.MergeNoConflictLabels(map[string]string{
oam.LabelNamespaceOfEnvName: env.Name,
}))
if err != nil {
if velaerr.IsLabelConflict(err) {
return bcode.ErrEnvNamespaceAlreadyBound
}
log.Logger.Errorf("update namespace label failure %s", err.Error())
return bcode.ErrEnvNamespaceFail
}
if err = ds.Add(ctx, env); err != nil {
return err
}
return nil
}
func getEnv(ctx context.Context, ds datastore.DataStore, envName string) (*model.Env, error) {
env := &model.Env{}
env.Name = envName
if err := ds.Get(ctx, env); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrEnvNotExisted
}
return nil, err
}
return env, nil
}
func listEnvs(ctx context.Context, ds datastore.DataStore, project string, listOption *datastore.ListOptions) ([]*model.Env, error) {
var env = model.Env{}
if project != "" {
env.Project = project
}
entities, err := ds.List(ctx, &env, listOption)
if err != nil {
return nil, err
}
var envs []*model.Env
for _, entity := range entities {
apienv, ok := entity.(*model.Env)
if !ok {
continue
}
envs = append(envs, apienv)
}
return envs, nil
}

View File

@@ -0,0 +1,122 @@
/*
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 usecase
import (
"context"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/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"
)
var _ = Describe("Test env usecase functions", func() {
var (
envUsecase *envUsecaseImpl
ds datastore.DataStore
)
BeforeEach(func() {
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "env-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{kubeClient: k8sClient, ds: ds}
})
It("Test Create/Get/Delete Env function", func() {
// create target
err := ds.Add(context.TODO(), &model.Target{Name: "env-test"})
Expect(err).Should(BeNil())
req := apisv1.CreateEnvRequest{
Name: "test-env",
Description: "this is a env description",
}
base, err := envUsecase.CreateEnv(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
Expect(cmp.Diff(base.Namespace, req.Name)).Should(BeEmpty())
By("test specified namespace to create env")
req2 := apisv1.CreateEnvRequest{
Name: "test-env-2",
Description: "this is a env description",
Namespace: base.Namespace,
}
_, err = envUsecase.CreateEnv(context.TODO(), req2)
equal := cmp.Equal(err, bcode.ErrEnvNamespaceAlreadyBound, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
req3 := apisv1.CreateEnvRequest{
Name: "test-env-2",
Description: "this is a env description",
Namespace: "default",
Project: "env-project",
Targets: []string{"env-test"},
}
base, err = envUsecase.CreateEnv(context.TODO(), req3)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Namespace, "default")).Should(BeEmpty())
var namespace corev1.Namespace
err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: base.Namespace}, &namespace)
Expect(err).Should(BeNil())
Expect(cmp.Diff(namespace.Labels[oam.LabelNamespaceOfEnvName], req3.Name)).Should(BeEmpty())
// test env target conflict
req4 := apisv1.CreateEnvRequest{
Name: "test-env-3",
Description: "this is a env description",
Namespace: "default",
Project: "env-project",
Targets: []string{"env-test"},
}
_, err = envUsecase.CreateEnv(context.TODO(), req4)
Expect(cmp.Equal(err, bcode.ErrEnvTargetConflict, cmpopts.EquateErrors())).Should(BeTrue())
// test update env
req5 := apisv1.UpdateEnvRequest{
Description: "this is a env description update",
Targets: []string{"env-test"},
}
env, err := envUsecase.UpdateEnv(context.TODO(), "test-env-2", req5)
Expect(err).Should(BeNil())
Expect(cmp.Diff(env.Description, req5.Description)).Should(BeEmpty())
// clean up the env
err = envUsecase.DeleteEnv(context.TODO(), "test-env")
Expect(err).Should(BeNil())
err = envUsecase.DeleteEnv(context.TODO(), "test-env-2")
Expect(err).Should(BeNil())
By("Test ListEnvs function")
_, err = envUsecase.ListEnvs(context.TODO(), 1, 1, apisv1.ListEnvOptions{})
Expect(err).Should(BeNil())
})
It("test checkEqual", func() {
Expect(checkEqual([]string{"default"}, []string{"default", "dev"})).Should(BeFalse())
Expect(checkEqual([]string{"default"}, []string{"default"})).Should(BeTrue())
})
})

View File

@@ -33,8 +33,6 @@ import (
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"
"github.com/oam-dev/kubevela/pkg/oam/util"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
)
const (
@@ -52,26 +50,25 @@ const (
type EnvBindingUsecase interface {
GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error)
GetEnvBinding(ctx context.Context, app *model.Application, envName string) (*model.EnvBinding, error)
CheckAppEnvBindingsContainTarget(ctx context.Context, app *model.Application, targetName string) (bool, error)
CreateEnvBinding(ctx context.Context, app *model.Application, env apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error)
CreateEnvBinding(ctx context.Context, app *model.Application, env apisv1.CreateApplicationEnvbindingRequest) (*apisv1.EnvBinding, error)
BatchCreateEnvBinding(ctx context.Context, app *model.Application, env apisv1.EnvBindingList) error
UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, diff apisv1.PutApplicationEnvRequest) (*apisv1.DetailEnvBindingResponse, error)
UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, diff apisv1.PutApplicationEnvBindingRequest) (*apisv1.DetailEnvBindingResponse, error)
DeleteEnvBinding(ctx context.Context, app *model.Application, envName string) error
BatchDeleteEnvBinding(ctx context.Context, app *model.Application) error
DetailEnvBinding(ctx context.Context, app *model.Application, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error)
ApplicationEnvRecycle(ctx context.Context, appModel *model.Application, envBinding *model.EnvBinding) error
GetSuitableType(ctx context.Context, app *model.Application) string
}
type envBindingUsecaseImpl struct {
ds datastore.DataStore
workflowUsecase WorkflowUsecase
envUsecase EnvUsecase
definitionUsecase DefinitionUsecase
kubeClient client.Client
}
// NewEnvBindingUsecase new envBinding usecase
func NewEnvBindingUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase, definitionUsecase DefinitionUsecase) EnvBindingUsecase {
func NewEnvBindingUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase, definitionUsecase DefinitionUsecase, envUsecase EnvUsecase) EnvBindingUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
@@ -81,32 +78,55 @@ func NewEnvBindingUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecas
workflowUsecase: workflowUsecase,
definitionUsecase: definitionUsecase,
kubeClient: kubecli,
envUsecase: envUsecase,
}
}
func (e *envBindingUsecaseImpl) GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error) {
var envBinding = model.EnvBinding{
AppPrimaryKey: app.PrimaryKey(),
func pickEnv(envs []*model.Env, name string) (*model.Env, error) {
for _, e := range envs {
if e.Name == name {
return e, nil
}
}
envBindings, err := e.ds.List(ctx, &envBinding, &datastore.ListOptions{})
return nil, bcode.ErrEnvNotExisted
}
func listFullEnvBinding(ctx context.Context, ds datastore.DataStore, option envListOption) ([]*apisv1.EnvBindingBase, error) {
envBindings, err := listEnvBindings(ctx, ds, option)
if err != nil {
return nil, bcode.ErrEnvBindingsNotExist
}
deliveryTarget := model.DeliveryTarget{
Namespace: app.Namespace,
targets, err := listTarget(ctx, ds, nil)
if err != nil {
return nil, err
}
deliveryTargets, err := e.ds.List(ctx, &deliveryTarget, &datastore.ListOptions{})
// TODO: list by project
envs, err := listEnvs(ctx, ds, "", nil)
if err != nil {
return nil, err
}
var list []*apisv1.EnvBindingBase
for _, ebd := range envBindings {
eb := ebd.(*model.EnvBinding)
list = append(list, convertEnvbindingModelToBase(app, eb, deliveryTargets))
for _, eb := range envBindings {
env, err := pickEnv(envs, eb.Name)
if err != nil {
log.Logger.Errorf("envbinding invalid %s", err.Error())
continue
}
list = append(list, convertEnvbindingModelToBase(eb, env, targets))
}
return list, nil
}
func (e *envBindingUsecaseImpl) GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error) {
full, err := listFullEnvBinding(ctx, e.ds, envListOption{appPrimaryKey: app.PrimaryKey()})
if err != nil {
log.Logger.Errorf("list envbinding for app %s err: %v\n", app.Name, err)
return nil, err
}
return full, nil
}
func (e *envBindingUsecaseImpl) GetEnvBinding(ctx context.Context, app *model.Application, envName string) (*model.EnvBinding, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envName)
if err != nil {
@@ -118,11 +138,8 @@ func (e *envBindingUsecaseImpl) GetEnvBinding(ctx context.Context, app *model.Ap
return envBinding, nil
}
func (e *envBindingUsecaseImpl) CheckAppEnvBindingsContainTarget(ctx context.Context, app *model.Application, targetName string) (bool, error) {
envBindings, err := e.GetEnvBindings(ctx, app)
if err != nil {
return false, err
}
// CheckAppEnvBindingsContainTarget check envbinding contain target
func CheckAppEnvBindingsContainTarget(envBindings []*apisv1.EnvBindingBase, targetName string) (bool, error) {
var filteredList []*apisv1.EnvBindingBase
for _, envBinding := range envBindings {
if utils.StringsContain(envBinding.TargetNames, targetName) {
@@ -132,7 +149,7 @@ func (e *envBindingUsecaseImpl) CheckAppEnvBindingsContainTarget(ctx context.Con
return len(filteredList) > 0, nil
}
func (e *envBindingUsecaseImpl) CreateEnvBinding(ctx context.Context, app *model.Application, envReq apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error) {
func (e *envBindingUsecaseImpl) CreateEnvBinding(ctx context.Context, app *model.Application, envReq apisv1.CreateApplicationEnvbindingRequest) (*apisv1.EnvBinding, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envReq.Name)
if err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
@@ -142,26 +159,38 @@ func (e *envBindingUsecaseImpl) CreateEnvBinding(ctx context.Context, app *model
if envBinding != nil {
return nil, bcode.ErrEnvBindingExist
}
envBindingModel := convertCreateReqToEnvBindingModel(app, envReq)
if err := e.ds.Add(ctx, &envBindingModel); err != nil {
return nil, err
}
err = e.createEnvWorkflow(ctx, app, &envBindingModel, false)
env, err := getEnv(ctx, e.ds, envReq.Name)
if err != nil {
return nil, err
}
envBindingModel := convertCreateReqToEnvBindingModel(app, envReq)
err = e.createEnvWorkflow(ctx, app, env, false)
if err != nil {
return nil, err
}
if err := e.ds.Add(ctx, &envBindingModel); err != nil {
return nil, err
}
return &envReq.EnvBinding, nil
}
func (e *envBindingUsecaseImpl) BatchCreateEnvBinding(ctx context.Context, app *model.Application, envbindings apisv1.EnvBindingList) error {
for i := range envbindings {
envBindingModel := convertToEnvBindingModel(app, *envbindings[i])
if err := e.ds.Add(ctx, envBindingModel); err != nil {
return err
}
err := e.createEnvWorkflow(ctx, app, envBindingModel, i == 0)
env, err := getEnv(ctx, e.ds, envBindingModel.Name)
if err != nil {
return err
log.Logger.Errorf("get env failure %s", err.Error())
continue
}
if err := e.ds.Add(ctx, envBindingModel); err != nil {
log.Logger.Errorf("add envbinding %s failure %s", envBindingModel.Name, err.Error())
continue
}
err = e.createEnvWorkflow(ctx, app, env, i == 0)
if err != nil {
log.Logger.Errorf("create env workflow failure %s", err.Error())
continue
}
}
return nil
@@ -179,7 +208,7 @@ func (e *envBindingUsecaseImpl) getBindingByEnv(ctx context.Context, app *model.
return &envBinding, nil
}
func (e *envBindingUsecaseImpl) UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, envUpdate apisv1.PutApplicationEnvRequest) (*apisv1.DetailEnvBindingResponse, error) {
func (e *envBindingUsecaseImpl) UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, _ apisv1.PutApplicationEnvBindingRequest) (*apisv1.DetailEnvBindingResponse, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envName)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
@@ -187,13 +216,16 @@ func (e *envBindingUsecaseImpl) UpdateEnvBinding(ctx context.Context, app *model
}
return nil, err
}
convertUpdateReqToEnvBindingModel(envBinding, envUpdate)
env, err := getEnv(ctx, e.ds, envName)
if err != nil {
return nil, err
}
// update env
if err := e.ds.Put(ctx, envBinding); err != nil {
return nil, err
}
// update env workflow
if err := e.updateEnvWorkflow(ctx, app, envBinding); err != nil {
if err := UpdateEnvWorkflow(ctx, e.kubeClient, e.ds, app, env); err != nil {
return nil, bcode.ErrEnvBindingUpdateWorkflow
}
return e.DetailEnvBinding(ctx, app, envBinding)
@@ -207,14 +239,20 @@ func (e *envBindingUsecaseImpl) DeleteEnvBinding(ctx context.Context, appModel *
}
return err
}
var app v1beta1.Application
err = e.kubeClient.Get(ctx, types.NamespacedName{Namespace: appModel.Namespace, Name: convertAppName(appModel.Name, envBinding.Name)}, &app)
if err == nil || !apierrors.IsNotFound(err) {
return bcode.ErrApplicationEnvRefusedDelete
}
if err := e.ds.Delete(ctx, &model.EnvBinding{AppPrimaryKey: appModel.PrimaryKey(), Name: envBinding.Name}); err != nil {
env, err := getEnv(ctx, e.ds, envName)
if err != nil && errors.Is(err, datastore.ErrRecordNotExist) {
return err
}
if env != nil {
var app v1beta1.Application
err = e.kubeClient.Get(ctx, types.NamespacedName{Namespace: env.Namespace, Name: appModel.Name}, &app)
if err == nil || !apierrors.IsNotFound(err) {
return bcode.ErrApplicationEnvRefusedDelete
}
if err := e.ds.Delete(ctx, &model.EnvBinding{AppPrimaryKey: appModel.PrimaryKey(), Name: envBinding.Name}); err != nil {
return err
}
}
// delete env workflow
if err := e.deleteEnvWorkflow(ctx, appModel, convertWorkflowName(envBinding.Name)); err != nil {
return err
@@ -241,8 +279,8 @@ func (e *envBindingUsecaseImpl) BatchDeleteEnvBinding(ctx context.Context, app *
return nil
}
func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *model.Application, env *model.EnvBinding, isDefault bool) error {
steps := e.genEnvWorkflowSteps(ctx, env, app)
func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *model.Application, env *model.Env, isDefault bool) error {
steps := GenEnvWorkflowSteps(ctx, e.kubeClient, e.ds, env, app)
_, err := e.workflowUsecase.CreateOrUpdateWorkflow(ctx, app, apisv1.CreateWorkflowRequest{
Name: convertWorkflowName(env.Name),
Alias: fmt.Sprintf("%s Workflow", env.Alias),
@@ -257,48 +295,6 @@ func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *mode
return nil
}
func (e *envBindingUsecaseImpl) updateEnvWorkflow(ctx context.Context, app *model.Application, env *model.EnvBinding) error {
// The existing step configuration should be maintained and the delivery target steps should be automatically updated.
envSteps := e.genEnvWorkflowSteps(ctx, env, app)
workflow, err := e.workflowUsecase.GetWorkflow(ctx, app, convertWorkflowName(env.Name))
if err != nil {
return err
}
var envStepNames = env.TargetNames
var workflowStepNames []string
for _, step := range workflow.Steps {
if isEnvStepType(step.Type) {
workflowStepNames = append(workflowStepNames, step.Name)
}
}
var filteredSteps []apisv1.WorkflowStep
_, readyToDeleteSteps, readyToAddSteps := compareSlices(workflowStepNames, envStepNames)
for _, step := range workflow.Steps {
if isEnvStepType(step.Type) && utils.StringsContain(readyToDeleteSteps, step.Name) {
continue
}
filteredSteps = append(filteredSteps, convertFromWorkflowStepModel(step))
}
for _, step := range envSteps {
if isEnvStepType(step.Type) && utils.StringsContain(readyToAddSteps, step.Name) {
filteredSteps = append(filteredSteps, step)
}
}
_, err = e.workflowUsecase.UpdateWorkflow(ctx, workflow, apisv1.UpdateWorkflowRequest{
Steps: filteredSteps,
Description: workflow.Description,
})
if err != nil {
return err
}
return nil
}
func (e *envBindingUsecaseImpl) deleteEnvWorkflow(ctx context.Context, app *model.Application, workflowName string) error {
if err := e.workflowUsecase.DeleteWorkflow(ctx, app, workflowName); err != nil {
if !errors.Is(err, bcode.ErrWorkflowNotExist) {
@@ -309,21 +305,26 @@ func (e *envBindingUsecaseImpl) deleteEnvWorkflow(ctx context.Context, app *mode
}
func (e *envBindingUsecaseImpl) DetailEnvBinding(ctx context.Context, app *model.Application, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) {
deliveryTarget := model.DeliveryTarget{
Namespace: app.Namespace,
targets, err := listTarget(ctx, e.ds, nil)
if err != nil {
return nil, err
}
deliveryTargets, err := e.ds.List(ctx, &deliveryTarget, &datastore.ListOptions{})
env, err := getEnv(ctx, e.ds, envBinding.Name)
if err != nil {
return nil, err
}
return &apisv1.DetailEnvBindingResponse{
EnvBindingBase: *convertEnvbindingModelToBase(app, envBinding, deliveryTargets),
EnvBindingBase: *convertEnvbindingModelToBase(envBinding, env, targets),
}, nil
}
func (e *envBindingUsecaseImpl) ApplicationEnvRecycle(ctx context.Context, appModel *model.Application, envBinding *model.EnvBinding) error {
env, err := getEnv(ctx, e.ds, envBinding.Name)
if err != nil {
return err
}
var app v1beta1.Application
err := e.kubeClient.Get(ctx, types.NamespacedName{Namespace: appModel.Namespace, Name: convertAppName(appModel.Name, envBinding.Name)}, &app)
err = e.kubeClient.Get(ctx, types.NamespacedName{Namespace: env.Namespace, Name: appModel.Name}, &app)
if err != nil {
if apierrors.IsNotFound(err) {
return nil
@@ -340,25 +341,21 @@ func (e *envBindingUsecaseImpl) ApplicationEnvRecycle(ctx context.Context, appMo
return nil
}
func convertCreateReqToEnvBindingModel(app *model.Application, req apisv1.CreateApplicationEnvRequest) model.EnvBinding {
func convertCreateReqToEnvBindingModel(app *model.Application, req apisv1.CreateApplicationEnvbindingRequest) model.EnvBinding {
envBinding := model.EnvBinding{
AppPrimaryKey: app.Name,
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
TargetNames: req.TargetNames,
}
return envBinding
}
func convertEnvbindingModelToBase(app *model.Application, envBinding *model.EnvBinding, deliveryTargets []datastore.Entity) *apisv1.EnvBindingBase {
var dtMap = make(map[string]*model.DeliveryTarget, len(deliveryTargets))
for _, dte := range deliveryTargets {
dt := dte.(*model.DeliveryTarget)
dtMap[dt.Name] = dt
func convertEnvbindingModelToBase(envBinding *model.EnvBinding, env *model.Env, targets []*model.Target) *apisv1.EnvBindingBase {
var dtMap = make(map[string]*model.Target, len(targets))
for _, dte := range targets {
dtMap[dte.Name] = dte
}
var targets []apisv1.EnvBindingTarget
for _, targetName := range envBinding.TargetNames {
var envBindingTargets []apisv1.EnvBindingTarget
for _, targetName := range env.Targets {
dt := dtMap[targetName]
if dt != nil {
ebt := apisv1.EnvBindingTarget{
@@ -370,108 +367,31 @@ func convertEnvbindingModelToBase(app *model.Application, envBinding *model.EnvB
Namespace: dt.Cluster.Namespace,
}
}
targets = append(targets, ebt)
envBindingTargets = append(envBindingTargets, ebt)
}
}
ebb := &apisv1.EnvBindingBase{
Name: envBinding.Name,
Alias: envBinding.Alias,
Description: envBinding.Description,
TargetNames: envBinding.TargetNames,
Targets: targets,
ComponentSelector: (*apisv1.ComponentSelector)(envBinding.ComponentSelector),
CreateTime: envBinding.CreateTime,
UpdateTime: envBinding.UpdateTime,
AppDeployName: convertAppName(app.Name, envBinding.Name),
Name: envBinding.Name,
Alias: env.Alias,
Description: env.Description,
TargetNames: env.Targets,
Targets: envBindingTargets,
CreateTime: envBinding.CreateTime,
UpdateTime: envBinding.UpdateTime,
AppDeployName: envBinding.AppPrimaryKey,
AppDeployNamespace: env.Namespace,
}
return ebb
}
func convertUpdateReqToEnvBindingModel(envBinding *model.EnvBinding, envUpdate apisv1.PutApplicationEnvRequest) *model.EnvBinding {
envBinding.Alias = envUpdate.Alias
envBinding.Description = envUpdate.Description
envBinding.TargetNames = envUpdate.TargetNames
if envUpdate.ComponentSelector != nil {
envBinding.ComponentSelector = (*model.ComponentSelector)(envUpdate.ComponentSelector)
}
return envBinding
}
func convertToEnvBindingModel(app *model.Application, envBind apisv1.EnvBinding) *model.EnvBinding {
re := model.EnvBinding{
AppPrimaryKey: app.Name,
Name: envBind.Name,
Description: envBind.Description,
Alias: envBind.Alias,
TargetNames: envBind.TargetNames,
}
if envBind.ComponentSelector != nil {
re.ComponentSelector = (*model.ComponentSelector)(envBind.ComponentSelector)
}
return &re
}
func (e *envBindingUsecaseImpl) GetSuitableType(ctx context.Context, app *model.Application) string {
components, err := e.ds.List(ctx, &model.ApplicationComponent{AppPrimaryKey: app.PrimaryKey()}, &datastore.ListOptions{PageSize: 1, Page: 1})
if err != nil {
log.Logger.Errorf("list application component list failure %s", err.Error())
}
if len(components) > 0 {
component := components[0].(*model.ApplicationComponent)
definition, err := e.definitionUsecase.GetComponentDefinition(ctx, component.Type)
if err != nil {
log.Logger.Errorf("get component definition %s failure %s", component.Type, err.Error())
}
if definition != nil {
if definition.Spec.Workload.Type == TerraformWorkfloadType {
return DeployCloudResource
}
if definition.Spec.Workload.Definition.Kind == TerraformWorkfloadKind {
return DeployCloudResource
}
}
}
return Deploy2Env
}
func (e *envBindingUsecaseImpl) genEnvWorkflowSteps(ctx context.Context, env *model.EnvBinding, app *model.Application) []apisv1.WorkflowStep {
var workflowSteps []v1beta1.WorkflowStep
for _, targetName := range env.TargetNames {
step := v1beta1.WorkflowStep{
Name: genPolicyEnvName(targetName),
Type: e.GetSuitableType(ctx, app),
Properties: util.Object2RawExtension(map[string]string{
"policy": genPolicyName(env.Name),
"env": genPolicyEnvName(targetName),
}),
}
workflowSteps = append(workflowSteps, step)
}
var steps []apisv1.WorkflowStep
for _, step := range workflowSteps {
var propertyStr string
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", utils2.Sanitize(app.Name), utils2.Sanitize(step.Name), err.Error())
continue
}
propertyStr = properties.JSON()
}
steps = append(steps, apisv1.WorkflowStep{
Name: step.Name,
Type: step.Type,
Alias: fmt.Sprintf("Deploy To %s", step.Name),
Description: fmt.Sprintf("deploy app to delivery target %s", step.Name),
DependsOn: step.DependsOn,
Properties: propertyStr,
Inputs: step.Inputs,
Outputs: step.Outputs,
})
}
return steps
}
func convertWorkflowName(envName string) string {
return fmt.Sprintf("workflow-%s", envName)
}

View File

@@ -0,0 +1,52 @@
/*
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 usecase
import (
"context"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
)
type envListOption struct {
appPrimaryKey string
envName string
}
func listEnvBindings(ctx context.Context, ds datastore.DataStore, listOption envListOption) ([]*model.EnvBinding, error) {
var envBinding = model.EnvBinding{}
if listOption.appPrimaryKey != "" {
envBinding.AppPrimaryKey = listOption.appPrimaryKey
}
if listOption.envName != "" {
envBinding.Name = listOption.envName
}
envBindings, err := ds.List(ctx, &envBinding, &datastore.ListOptions{})
if err != nil {
return nil, err
}
var ret []*model.EnvBinding
for _, et := range envBindings {
eb, ok := et.(*model.EnvBinding)
if !ok {
continue
}
ret = append(ret, eb)
}
return ret, nil
}

View File

@@ -23,59 +23,74 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"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"
)
var _ = Describe("Test envBindingUsecase functions", func() {
var (
envUsecase *envUsecaseImpl
envBindingUsecase *envBindingUsecaseImpl
workflowUsecase *workflowUsecaseImpl
definitionUsecase DefinitionUsecase
envBindingDemo1 apisv1.EnvBinding
envBindingDemo2 apisv1.EnvBinding
testApp *model.Application
ds datastore.DataStore
)
BeforeEach(func() {
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "env-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
testApp = &model.Application{
Name: "test-app-env",
Namespace: "default",
Name: "test-app-env",
}
workflowUsecase = &workflowUsecaseImpl{ds: ds, kubeClient: k8sClient}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{ds: ds, kubeClient: k8sClient, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient, caches: make(map[string]*utils.MemoryCache)}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, workflowUsecase: workflowUsecase, definitionUsecase: definitionUsecase, kubeClient: k8sClient}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, workflowUsecase: workflowUsecase, definitionUsecase: definitionUsecase, kubeClient: k8sClient, envUsecase: envUsecase}
envBindingDemo1 = apisv1.EnvBinding{
Name: "dev",
Alias: "dev alias",
TargetNames: []string{"dev-target"},
Name: "envbinding-dev",
}
envBindingDemo2 = apisv1.EnvBinding{
Name: "prod",
Alias: "prod alias",
TargetNames: []string{"prod-target"},
Name: "envbinding-prod",
}
})
It("Test Create Application Env function", func() {
// create target
err := ds.Add(context.TODO(), &model.Target{Name: "dev-target"})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Target{Name: "prod-target"})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "envbinding-dev", Targets: []string{"dev-target"}})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "envbinding-prod", Targets: []string{"prod-target"}})
Expect(err).Should(BeNil())
By("create two envbinding")
req := apisv1.CreateApplicationEnvRequest{EnvBinding: envBindingDemo1}
req := apisv1.CreateApplicationEnvbindingRequest{EnvBinding: envBindingDemo1}
base, err := envBindingUsecase.CreateEnvBinding(context.TODO(), testApp, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
req = apisv1.CreateApplicationEnvRequest{EnvBinding: envBindingDemo2}
req = apisv1.CreateApplicationEnvbindingRequest{EnvBinding: envBindingDemo2}
base, err = envBindingUsecase.CreateEnvBinding(context.TODO(), testApp, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
By("auto create two workflow")
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), testApp, "workflow-dev")
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), testApp, convertWorkflowName("envbinding-dev"))
Expect(err).Should(BeNil())
Expect(cmp.Diff(workflow.Steps[0].Name, "dev-target")).Should(BeEmpty())
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, "workflow-prod")
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, convertWorkflowName("envbinding-prod"))
Expect(err).Should(BeNil())
Expect(cmp.Diff(workflow.Steps[0].Name, "prod-target")).Should(BeEmpty())
})
@@ -88,56 +103,37 @@ var _ = Describe("Test envBindingUsecase functions", func() {
})
It("Test GetApplication Env function", func() {
envBinding, err := envBindingUsecase.GetEnvBinding(context.TODO(), testApp, "dev")
envBinding, err := envBindingUsecase.GetEnvBinding(context.TODO(), testApp, "envbinding-dev")
Expect(err).Should(BeNil())
Expect(envBinding).ShouldNot(BeNil())
Expect(cmp.Diff(envBinding.Name, "dev")).Should(BeEmpty())
})
It("Test CheckAppEnvBindingsContainTarget function", func() {
isContain, err := envBindingUsecase.CheckAppEnvBindingsContainTarget(context.TODO(), testApp, "dev-target")
Expect(err).Should(BeNil())
Expect(isContain).ShouldNot(BeNil())
Expect(cmp.Diff(isContain, true)).Should(BeEmpty())
Expect(cmp.Diff(envBinding.Name, "envbinding-dev")).Should(BeEmpty())
})
It("Test Application UpdateEnv function", func() {
envBinding, err := envBindingUsecase.UpdateEnvBinding(context.TODO(), testApp, "prod", apisv1.PutApplicationEnvRequest{
TargetNames: []string{"prod-target-new1", "prod-target-new2"},
})
Expect(err).Should(BeNil())
Expect(envBinding).ShouldNot(BeNil())
Expect(cmp.Diff(envBinding.TargetNames[0], "prod-target-new1")).Should(BeEmpty())
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), testApp, "workflow-prod")
Expect(err).Should(BeNil())
Expect(cmp.Diff(workflow.Steps[0].Name, "prod-target-new1")).Should(BeEmpty())
envBinding, err = envBindingUsecase.UpdateEnvBinding(context.TODO(), testApp, "prod", apisv1.PutApplicationEnvRequest{
TargetNames: []string{"prod-target-new3", "prod-target-new2"},
})
envBinding, err := envBindingUsecase.UpdateEnvBinding(context.TODO(), testApp, "envbinding-prod", apisv1.PutApplicationEnvBindingRequest{})
Expect(err).Should(BeNil())
Expect(envBinding).ShouldNot(BeNil())
Expect(cmp.Diff(envBinding.TargetNames[0], "prod-target-new3")).Should(BeEmpty())
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, "workflow-prod")
Expect(cmp.Diff(envBinding.TargetNames[0], "prod-target")).Should(BeEmpty())
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), testApp, "workflow-envbinding-prod")
Expect(err).Should(BeNil())
Expect(cmp.Diff(workflow.Steps[1].Name, "prod-target-new3")).Should(BeEmpty())
Expect(cmp.Diff(workflow.Steps[0].Name, "prod-target")).Should(BeEmpty())
})
It("Test Application DeleteEnv function", func() {
err := envBindingUsecase.DeleteEnvBinding(context.TODO(), testApp, "dev")
err := envBindingUsecase.DeleteEnvBinding(context.TODO(), testApp, "envbinding-dev")
Expect(err).Should(BeNil())
_, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, "dev")
_, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, convertWorkflowName("envbinding-dev"))
Expect(err).ShouldNot(BeNil())
err = envBindingUsecase.DeleteEnvBinding(context.TODO(), testApp, "prod")
err = envBindingUsecase.DeleteEnvBinding(context.TODO(), testApp, "envbinding-prod")
Expect(err).Should(BeNil())
_, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, "prod")
_, err = workflowUsecase.GetWorkflow(context.TODO(), testApp, convertWorkflowName("envbinding-prod"))
Expect(err).ShouldNot(BeNil())
})
It("Test Application BatchCreateEnv function", func() {
testBatchApp := &model.Application{
Name: "test-batch-createt",
Namespace: "default",
Name: "test-batch-createt",
}
err := envBindingUsecase.BatchCreateEnvBinding(context.TODO(), testBatchApp, apisv1.EnvBindingList{&envBindingDemo1, &envBindingDemo2})
Expect(err).Should(BeNil())

View File

@@ -19,12 +19,7 @@ package usecase
import (
"context"
"errors"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
@@ -33,11 +28,23 @@ import (
"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"
"github.com/oam-dev/kubevela/pkg/multicluster"
)
// ProjectNamespace mark the usage of the namespace.
const ProjectNamespace = "project"
const (
// DefaultInitName is default object name for initialization
DefaultInitName = "default"
// DefaultInitNamespace is default namespace name for initialization
DefaultInitNamespace = "default"
// DefaultTargetDescription describes default target created
DefaultTargetDescription = "Default target is created by velaux system automatically."
// DefaultEnvDescription describes default env created
DefaultEnvDescription = "Default environment is created by velaux system automatically."
// DefaultProjectDescription describes the default project created
DefaultProjectDescription = "Default project is created by velaux system automatically."
)
// ProjectUsecase project manage usecase.
type ProjectUsecase interface {
@@ -47,17 +54,80 @@ type ProjectUsecase interface {
}
type projectUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
ds datastore.DataStore
k8sClient client.Client
}
// NewProjectUsecase new project usecase
func NewProjectUsecase(ds datastore.DataStore) ProjectUsecase {
kubecli, err := clients.GetKubeClient()
k8sClient, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
log.Logger.Fatalf("get k8sClient failure: %s", err.Error())
}
p := &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
p.initDefaultProjectEnvTarget(DefaultInitNamespace)
return p
}
// initDefaultProjectEnvTarget will initialize a default project with a default env that contain a default target
// the default env and default target both using the `default` namespace in control plane cluster
func (p *projectUsecaseImpl) initDefaultProjectEnvTarget(defaultNamespace string) {
ctx := context.Background()
entities, err := listProjects(ctx, p.ds)
if err != nil {
log.Logger.Errorf("initialize project failed %v", err)
return
}
if len(entities) > 0 {
return
}
log.Logger.Info("no default project found, adding a default project with default env and target")
if err := createTargetNamespace(ctx, p.k8sClient, multicluster.ClusterLocalName, defaultNamespace, DefaultInitName); err != nil {
log.Logger.Errorf("initialize default target namespace failed %v", err)
return
}
// initialize default target first
err = createTarget(ctx, p.ds, &model.Target{
Name: DefaultInitName,
Alias: "Default",
Description: DefaultTargetDescription,
Cluster: &model.ClusterTarget{
ClusterName: multicluster.ClusterLocalName,
Namespace: defaultNamespace,
},
})
// for idempotence, ignore default target already exist error
if err != nil && errors.Is(err, bcode.ErrTargetExist) {
log.Logger.Errorf("initialize default target failed %v", err)
return
}
// initialize default target first
err = createEnv(ctx, p.k8sClient, p.ds, &model.Env{
Name: DefaultInitName,
Alias: "Default",
Description: DefaultEnvDescription,
Project: DefaultInitName,
Namespace: defaultNamespace,
Targets: []string{DefaultInitName},
})
// for idempotence, ignore default env already exist error
if err != nil && errors.Is(err, bcode.ErrEnvAlreadyExists) {
log.Logger.Errorf("initialize default environment failed %v", err)
return
}
_, err = p.CreateProject(ctx, apisv1.CreateProjectRequest{
Name: DefaultInitName,
Alias: "Default",
Description: DefaultProjectDescription,
})
if err != nil {
log.Logger.Errorf("initialize project failed %v", err)
return
}
return &projectUsecaseImpl{kubeClient: kubecli, ds: ds}
}
// GetProject get project
@@ -72,10 +142,9 @@ func (p *projectUsecaseImpl) GetProject(ctx context.Context, projectName string)
return project, nil
}
// ListProjects list projects
func (p *projectUsecaseImpl) ListProjects(ctx context.Context) ([]*apisv1.ProjectBase, error) {
func listProjects(ctx context.Context, ds datastore.DataStore) ([]*apisv1.ProjectBase, error) {
var project = model.Project{}
entitys, err := p.ds.List(ctx, &project, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
entitys, err := ds.List(ctx, &project, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
@@ -87,6 +156,20 @@ func (p *projectUsecaseImpl) ListProjects(ctx context.Context) ([]*apisv1.Projec
return projects, nil
}
// ListProjects list projects
func (p *projectUsecaseImpl) ListProjects(ctx context.Context) ([]*apisv1.ProjectBase, error) {
return listProjects(ctx, p.ds)
}
// DeleteProject delete a project
func (p *projectUsecaseImpl) DeleteProject(ctx context.Context, name string) error {
// TODO(@wonderflow): it's not supported for delete a project now, just used in test
// we should prevent delete a project that contain any application/env inside.
return p.ds.Delete(ctx, &model.Project{Name: name})
}
// CreateProject create project
func (p *projectUsecaseImpl) CreateProject(ctx context.Context, req apisv1.CreateProjectRequest) (*apisv1.ProjectBase, error) {
@@ -99,68 +182,28 @@ func (p *projectUsecaseImpl) CreateProject(ctx context.Context, req apisv1.Creat
return nil, bcode.ErrProjectIsExist
}
new := &model.Project{
newProject := &model.Project{
Name: req.Name,
Description: req.Description,
Alias: req.Alias,
Namespace: fmt.Sprintf("project-%s", req.Name),
}
if req.Namespace != "" {
new.Namespace = req.Namespace
}
// create namespace at first
if err := p.kubeClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: new.Namespace,
Labels: map[string]string{
oam.LabelProjectNamesapce: new.Name,
oam.LabelUsageNamespace: ProjectNamespace,
},
},
Spec: corev1.NamespaceSpec{},
}); err != nil {
if !apierrors.IsAlreadyExists(err) {
return nil, err
}
if apierrors.IsAlreadyExists(err) {
var namespace corev1.Namespace
if err := p.kubeClient.Get(ctx, k8stypes.NamespacedName{Name: new.Namespace}, &namespace); err != nil {
return nil, bcode.ErrProjectNamespaceFail
}
if _, exist := namespace.Labels[oam.LabelProjectNamesapce]; !exist {
if namespace.Labels == nil {
namespace.Labels = make(map[string]string)
}
namespace.Labels[oam.LabelProjectNamesapce] = new.Name
namespace.Labels[oam.LabelUsageNamespace] = ProjectNamespace
if err := p.kubeClient.Update(ctx, &namespace); err != nil {
log.Logger.Errorf("update namespace label failure %s", err.Error())
return nil, bcode.ErrProjectNamespaceFail
}
} else {
return nil, bcode.ErrProjectNamespaceIsExist
}
}
}
if err := p.ds.Add(ctx, new); err != nil {
if err := p.ds.Add(ctx, newProject); err != nil {
return nil, err
}
return &apisv1.ProjectBase{
Name: new.Name,
Alias: new.Alias,
Namespace: new.Namespace,
Description: new.Description,
CreateTime: new.CreateTime,
UpdateTime: new.UpdateTime,
Name: newProject.Name,
Alias: newProject.Alias,
Description: newProject.Description,
CreateTime: newProject.CreateTime,
UpdateTime: newProject.UpdateTime,
}, nil
}
func convertProjectModel2Base(project *model.Project) *apisv1.ProjectBase {
return &apisv1.ProjectBase{
Name: project.Name,
Namespace: project.Namespace,
Description: project.Description,
Alias: project.Alias,
CreateTime: project.CreateTime,

View File

@@ -18,28 +18,110 @@ package usecase
import (
"context"
"fmt"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/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/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test project usecase functions", func() {
var (
projectUsecase *projectUsecaseImpl
projectUsecase *projectUsecaseImpl
envImpl *envUsecaseImpl
targetImpl *targetUsecaseImpl
defaultNamespace = "project-default-ns1-test"
)
BeforeEach(func() {
projectUsecase = &projectUsecaseImpl{kubeClient: k8sClient, ds: ds}
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
var ns = corev1.Namespace{}
ns.Name = defaultNamespace
err = k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
projectUsecase = &projectUsecaseImpl{k8sClient: k8sClient, ds: ds}
pp, err := projectUsecase.ListProjects(context.TODO())
Expect(err).Should(BeNil())
// reset all projects
for _, p := range pp {
_ = projectUsecase.DeleteProject(context.TODO(), p.Name)
}
envImpl = &envUsecaseImpl{kubeClient: k8sClient, ds: ds}
envs, err := envImpl.ListEnvs(context.TODO(), 0, 0, apisv1.ListEnvOptions{})
Expect(err).Should(BeNil())
// reset all projects
for _, e := range envs {
_ = envImpl.DeleteEnv(context.TODO(), e.Name)
}
targetImpl = &targetUsecaseImpl{k8sClient: k8sClient, ds: ds}
targs, err := targetImpl.ListTargets(context.TODO(), 0, 0)
Expect(err).Should(BeNil())
// reset all projects
for _, e := range targs.Targets {
_ = envImpl.DeleteEnv(context.TODO(), e.Name)
}
})
It("Test Createproject function", func() {
It("Test project initialize function", func() {
projectUsecase.initDefaultProjectEnvTarget(defaultNamespace)
By("test env created")
var namespace corev1.Namespace
Eventually(func() error {
return k8sClient.Get(context.TODO(), types.NamespacedName{Name: defaultNamespace}, &namespace)
}, time.Second*3, time.Microsecond*300).Should(BeNil())
Expect(cmp.Diff(namespace.Labels[oam.LabelNamespaceOfEnvName], DefaultInitName)).Should(BeEmpty())
Expect(cmp.Diff(namespace.Labels[oam.LabelNamespaceOfTargetName], DefaultInitName)).Should(BeEmpty())
Expect(cmp.Diff(namespace.Labels[oam.LabelControlPlaneNamespaceUsage], oam.VelaNamespaceUsageEnv)).Should(BeEmpty())
Expect(cmp.Diff(namespace.Labels[oam.LabelRuntimeNamespaceUsage], oam.VelaNamespaceUsageTarget)).Should(BeEmpty())
By("check project created")
dp, err := projectUsecase.GetProject(context.TODO(), DefaultInitName)
Expect(err).Should(BeNil())
Expect(dp.Alias).Should(BeEquivalentTo("Default"))
Expect(dp.Description).Should(BeEquivalentTo(DefaultProjectDescription))
By("check env created")
env, err := envImpl.GetEnv(context.TODO(), DefaultInitName)
Expect(err).Should(BeNil())
Expect(env.Alias).Should(BeEquivalentTo("Default"))
Expect(env.Description).Should(BeEquivalentTo(DefaultEnvDescription))
Expect(env.Project).Should(BeEquivalentTo(DefaultInitName))
Expect(env.Targets).Should(BeEquivalentTo([]string{DefaultInitName}))
Expect(env.Namespace).Should(BeEquivalentTo(defaultNamespace))
By("check target created")
tg, err := targetImpl.GetTarget(context.TODO(), DefaultInitName)
Expect(err).Should(BeNil())
Expect(tg.Alias).Should(BeEquivalentTo("Default"))
Expect(tg.Description).Should(BeEquivalentTo(DefaultTargetDescription))
Expect(tg.Cluster).Should(BeEquivalentTo(&model.ClusterTarget{
ClusterName: multicluster.ClusterLocalName,
Namespace: defaultNamespace,
}))
Expect(env.Targets).Should(BeEquivalentTo([]string{DefaultInitName}))
Expect(env.Namespace).Should(BeEquivalentTo(defaultNamespace))
err = targetImpl.DeleteTarget(context.TODO(), DefaultInitName)
Expect(err).Should(BeNil())
err = envImpl.DeleteEnv(context.TODO(), DefaultInitName)
Expect(err).Should(BeNil())
})
It("Test Create project function", func() {
req := apisv1.CreateProjectRequest{
Name: "test-project",
Description: "this is a project description",
@@ -47,34 +129,9 @@ var _ = Describe("Test project usecase functions", func() {
base, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
Expect(cmp.Diff(base.Namespace, fmt.Sprintf("project-%s", req.Name))).Should(BeEmpty())
By("test specified namespace to create project")
req2 := apisv1.CreateProjectRequest{
Name: "test-project-2",
Description: "this is a project description",
Namespace: base.Namespace,
}
_, err = projectUsecase.CreateProject(context.TODO(), req2)
equal := cmp.Equal(err, bcode.ErrProjectNamespaceIsExist, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
req3 := apisv1.CreateProjectRequest{
Name: "test-project-2",
Description: "this is a project description",
Namespace: "default",
}
base, err = projectUsecase.CreateProject(context.TODO(), req3)
_, err = projectUsecase.ListProjects(context.TODO())
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Namespace, "default")).Should(BeEmpty())
var namespace corev1.Namespace
err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: base.Namespace}, &namespace)
Expect(err).Should(BeNil())
Expect(cmp.Diff(namespace.Labels[oam.LabelProjectNamesapce], req3.Name)).Should(BeEmpty())
projectUsecase.DeleteProject(context.TODO(), "test-project")
})
It("Test ListProject function", func() {
_, err := projectUsecase.ListProjects(context.TODO())
Expect(err).Should(BeNil())
})
})

View File

@@ -41,7 +41,11 @@ import (
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
var ds datastore.DataStore
func TestUsecase(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Usecase Suite")
}
var _ = BeforeSuite(func(done Done) {
rand.Seed(time.Now().UnixNano())
@@ -67,9 +71,7 @@ var _ = BeforeSuite(func(done Done) {
Expect(k8sClient).ToNot(BeNil())
By("new kube client success")
clients.SetKubeClient(k8sClient)
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "kubevela"})
Expect(err).Should(BeNil())
Expect(ds).ToNot(BeNil())
close(done)
}, 240)
@@ -97,11 +99,6 @@ func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
return ds, nil
}
func TestUsecase(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Usecase Suite")
}
func randomNamespaceName(basic string) string {
return fmt.Sprintf("%s-%s", basic, strconv.FormatInt(rand.Int63(), 16))
}

View File

@@ -0,0 +1,192 @@
/*
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 usecase
import (
"context"
"errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/bcode"
"github.com/oam-dev/kubevela/pkg/multicluster"
)
// TargetUsecase Target manage api
type TargetUsecase interface {
GetTarget(ctx context.Context, TargetName string) (*model.Target, error)
DetailTarget(ctx context.Context, Target *model.Target) (*apisv1.DetailTargetResponse, error)
DeleteTarget(ctx context.Context, TargetName string) error
CreateTarget(ctx context.Context, req apisv1.CreateTargetRequest) (*apisv1.DetailTargetResponse, error)
UpdateTarget(ctx context.Context, Target *model.Target, req apisv1.UpdateTargetRequest) (*apisv1.DetailTargetResponse, error)
ListTargets(ctx context.Context, page, pageSize int) (*apisv1.ListTargetResponse, error)
}
type targetUsecaseImpl struct {
ds datastore.DataStore
k8sClient client.Client
}
// NewTargetUsecase new Target usecase
func NewTargetUsecase(ds datastore.DataStore) TargetUsecase {
k8sClient, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get k8sClient failure: %s", err.Error())
}
return &targetUsecaseImpl{
k8sClient: k8sClient,
ds: ds,
}
}
func (dt *targetUsecaseImpl) ListTargets(ctx context.Context, page, pageSize int) (*apisv1.ListTargetResponse, error) {
Targets, err := listTarget(ctx, dt.ds, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
resp := &apisv1.ListTargetResponse{
Targets: []apisv1.TargetBase{},
}
for _, raw := range Targets {
resp.Targets = append(resp.Targets, *(dt.convertFromTargetModel(ctx, raw)))
}
count, err := dt.ds.Count(ctx, &model.Target{}, nil)
if err != nil {
return nil, err
}
resp.Total = count
return resp, nil
}
// DeleteTarget delete application Target
func (dt *targetUsecaseImpl) DeleteTarget(ctx context.Context, targetName string) error {
Target := &model.Target{
Name: targetName,
}
ddt, err := dt.GetTarget(ctx, targetName)
if errors.Is(err, datastore.ErrRecordExist) {
return nil
}
if err != nil {
return err
}
if err = deleteTargetNamespace(ctx, dt.k8sClient, ddt.Cluster.ClusterName, ddt.Cluster.Namespace, targetName); err != nil {
return err
}
if err = dt.ds.Delete(ctx, Target); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrTargetNotExist
}
return err
}
return nil
}
// CreateTarget will create a delivery target binding with a cluster and namespace, by default, it will use local cluster and namespace align with targetName
// TODO(@wonderflow): we should support empty target in the future which only delivery cloud resources
func (dt *targetUsecaseImpl) CreateTarget(ctx context.Context, req apisv1.CreateTargetRequest) (*apisv1.DetailTargetResponse, error) {
Target := convertCreateReqToTargetModel(req)
if req.Cluster == nil {
req.Cluster = &apisv1.ClusterTarget{ClusterName: multicluster.ClusterLocalName, Namespace: req.Name}
}
if err := createTargetNamespace(ctx, dt.k8sClient, req.Cluster.ClusterName, req.Cluster.Namespace, req.Name); err != nil {
return nil, err
}
err := createTarget(ctx, dt.ds, &Target)
if err != nil {
return nil, err
}
return dt.DetailTarget(ctx, &Target)
}
func (dt *targetUsecaseImpl) UpdateTarget(ctx context.Context, target *model.Target, req apisv1.UpdateTargetRequest) (*apisv1.DetailTargetResponse, error) {
TargetModel := convertUpdateReqToTargetModel(target, req)
if err := dt.ds.Put(ctx, TargetModel); err != nil {
return nil, err
}
return dt.DetailTarget(ctx, TargetModel)
}
// DetailTarget detail Target
func (dt *targetUsecaseImpl) DetailTarget(ctx context.Context, target *model.Target) (*apisv1.DetailTargetResponse, error) {
return &apisv1.DetailTargetResponse{
TargetBase: *dt.convertFromTargetModel(ctx, target),
}, nil
}
// GetTarget get Target model
func (dt *targetUsecaseImpl) GetTarget(ctx context.Context, targetName string) (*model.Target, error) {
Target := &model.Target{
Name: targetName,
}
if err := dt.ds.Get(ctx, Target); err != nil {
return nil, err
}
return Target, nil
}
func convertUpdateReqToTargetModel(target *model.Target, req apisv1.UpdateTargetRequest) *model.Target {
target.Alias = req.Alias
target.Description = req.Description
target.Variable = req.Variable
return target
}
func convertCreateReqToTargetModel(req apisv1.CreateTargetRequest) model.Target {
Target := model.Target{
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Cluster: (*model.ClusterTarget)(req.Cluster),
Variable: req.Variable,
}
return Target
}
func (dt *targetUsecaseImpl) convertFromTargetModel(ctx context.Context, target *model.Target) *apisv1.TargetBase {
var appNum int64 = 0
// TODO: query app num in target
targetBase := &apisv1.TargetBase{
Name: target.Name,
Alias: target.Alias,
Description: target.Description,
Cluster: (*apisv1.ClusterTarget)(target.Cluster),
Variable: target.Variable,
CreateTime: target.CreateTime,
UpdateTime: target.UpdateTime,
AppNum: appNum,
}
if targetBase.Cluster != nil && targetBase.Cluster.ClusterName != "" {
cluster, err := _getClusterFromDataStore(ctx, dt.ds, target.Cluster.ClusterName)
if err != nil {
log.Logger.Errorf("query cluster info failure %s", err.Error())
}
if cluster != nil {
targetBase.ClusterAlias = cluster.Alias
}
}
return targetBase
}

View File

@@ -0,0 +1,104 @@
/*
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 usecase
import (
"context"
apierror "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils"
velaerr "github.com/oam-dev/kubevela/pkg/utils/errors"
)
func createTargetNamespace(ctx context.Context, k8sClient client.Client, clusterName, namespace, targetName string) error {
err := utils.CreateOrUpdateNamespace(multicluster.ContextWithClusterName(ctx, clusterName), k8sClient, namespace, utils.MergeOverrideLabels(map[string]string{
oam.LabelRuntimeNamespaceUsage: oam.VelaNamespaceUsageTarget,
}), utils.MergeNoConflictLabels(map[string]string{
oam.LabelNamespaceOfTargetName: targetName,
}))
if velaerr.IsLabelConflict(err) {
log.Logger.Errorf("update namespace for target err %v", err)
return bcode.ErrTargetNamespaceAlreadyBound
}
if err != nil {
return err
}
return nil
}
func deleteTargetNamespace(ctx context.Context, k8sClient client.Client, clusterName, namespace, targetName string) error {
err := utils.UpdateNamespace(multicluster.ContextWithClusterName(ctx, clusterName), k8sClient, namespace,
// check no conflict label first to make sure the namespace belong to the target, then override it
utils.MergeNoConflictLabels(map[string]string{
oam.LabelNamespaceOfTargetName: targetName,
}),
utils.MergeOverrideLabels(map[string]string{
oam.LabelRuntimeNamespaceUsage: "",
oam.LabelNamespaceOfTargetName: "",
}))
if apierror.IsNotFound(err) {
return nil
}
return err
}
func createTarget(ctx context.Context, ds datastore.DataStore, tg *model.Target) error {
// check Target name.
exit, err := ds.IsExist(ctx, tg)
if err != nil {
log.Logger.Errorf("check target existence failure %s", err.Error())
return err
}
if exit {
return bcode.ErrTargetExist
}
if err = ds.Add(ctx, tg); err != nil {
log.Logger.Errorf("add target failure %s", err.Error())
return err
}
return nil
}
func listTarget(ctx context.Context, ds datastore.DataStore, dsOption *datastore.ListOptions) ([]*model.Target, error) {
if dsOption == nil {
dsOption = &datastore.ListOptions{}
}
Target := model.Target{}
Targets, err := ds.List(ctx, &Target, dsOption)
if err != nil {
log.Logger.Errorf("list target err %v", err)
return nil, err
}
var respTargets []*model.Target
for _, raw := range Targets {
target, ok := raw.(*model.Target)
if ok {
respTargets = append(respTargets, target)
}
}
return respTargets, nil
}

View File

@@ -23,62 +23,65 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"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"
)
var _ = Describe("Test delivery target usecase functions", func() {
var _ = Describe("Test target usecase functions", func() {
var (
deliveryTargetUsecase *deliveryTargetUsecaseImpl
projectUsecase *projectUsecaseImpl
testProject = "target-project"
targetUsecase *targetUsecaseImpl
projectUsecase *projectUsecaseImpl
testProject = "target-project"
)
BeforeEach(func() {
projectUsecase = &projectUsecaseImpl{ds: ds, kubeClient: k8sClient}
deliveryTargetUsecase = &deliveryTargetUsecaseImpl{ds: ds, projectUsecase: projectUsecase}
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
})
It("Test CreateDeliveryTarget function", func() {
It("Test CreateTarget function", func() {
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: testProject})
Expect(err).Should(BeNil())
req := apisv1.CreateDeliveryTargetRequest{
Name: "test-delivery-target",
Project: testProject,
req := apisv1.CreateTargetRequest{
Name: "test--target",
Alias: "test-alias",
Description: "this is a deliveryTarget",
Description: "this is a Target",
Cluster: &apisv1.ClusterTarget{ClusterName: "cluster-dev", Namespace: "dev"},
Variable: map[string]interface{}{"terraform-provider": "provider", "region": "us-1"},
}
base, err := deliveryTargetUsecase.CreateDeliveryTarget(context.TODO(), req)
base, err := targetUsecase.CreateTarget(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
Expect(deliveryTargetUsecase.ds.Add(context.TODO(), &model.Cluster{Name: "cluster-dev", Alias: "dev-alias"})).Should(Succeed())
})
Expect(targetUsecase.ds.Add(context.TODO(), &model.Cluster{Name: "cluster-dev", Alias: "dev-alias"})).Should(Succeed())
It("Test GetDeliveryTarget function", func() {
deliveryTarget, err := deliveryTargetUsecase.GetDeliveryTarget(context.TODO(), "test-delivery-target")
By("Test GetTarget function")
Target, err := targetUsecase.GetTarget(context.TODO(), "test--target")
Expect(err).Should(BeNil())
Expect(deliveryTarget).ShouldNot(BeNil())
Expect(cmp.Diff(deliveryTarget.Name, "test-delivery-target")).Should(BeEmpty())
})
Expect(Target).ShouldNot(BeNil())
Expect(cmp.Diff(Target.Name, "test--target")).Should(BeEmpty())
It("Test ListDeliveryTargets function", func() {
resp, err := deliveryTargetUsecase.ListDeliveryTargets(context.TODO(), 1, 1, "")
By("Test ListTargets function")
resp, err := targetUsecase.ListTargets(context.TODO(), 1, 1)
Expect(err).Should(BeNil())
Expect(resp.Targets[0].ClusterAlias).Should(Equal("dev-alias"))
})
It("Test DetailDeliveryTarget function", func() {
detail, err := deliveryTargetUsecase.DetailDeliveryTarget(context.TODO(),
&model.DeliveryTarget{
Name: "test-delivery-target",
Namespace: "test-namespace",
By("Test DetailTarget function")
detail, err := targetUsecase.DetailTarget(context.TODO(),
&model.Target{
Name: "test--target",
Alias: "test-alias",
Description: "this is a deliveryTarget",
Description: "this is a Target",
Cluster: &model.ClusterTarget{ClusterName: "cluster-dev", Namespace: "dev"},
Variable: map[string]interface{}{"terraform-provider": "provider", "region": "us-1"}})
Expect(err).Should(BeNil())
Expect(detail.Name).Should(Equal("test-delivery-target"))
Expect(detail.Name).Should(Equal("test--target"))
By("Test Delete target")
err = targetUsecase.DeleteTarget(context.TODO(), "test--target")
Expect(err).Should(BeNil())
})
})

View File

@@ -102,23 +102,6 @@
label: ReadinessProbe
sort: 13
subParameters:
- description: How often, in seconds, to execute the probe.
jsonKey: periodSeconds
label: PeriodSeconds
sort: 100
uiType: Number
validate:
defaultValue: 10
required: true
- description: Minimum consecutive successes for the probe to be considered successful
after having failed.
jsonKey: successThreshold
label: SuccessThreshold
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: 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
@@ -230,6 +213,23 @@
validate:
defaultValue: 0
required: true
- description: How often, in seconds, to execute the probe.
jsonKey: periodSeconds
label: PeriodSeconds
sort: 100
uiType: Number
validate:
defaultValue: 10
required: true
- description: Minimum consecutive successes for the probe to be considered successful
after having failed.
jsonKey: successThreshold
label: SuccessThreshold
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
uiType: Group
validate: {}
- description: Instructions for assessing whether the container is alive.
@@ -237,6 +237,34 @@
label: LivenessProbe
sort: 15
subParameters:
- description: Number of seconds after which the probe times out.
jsonKey: timeoutSeconds
label: TimeoutSeconds
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: 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.
jsonKey: exec
label: Exec
sort: 100
subParameters:
- description: 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.
jsonKey: command
label: Command
sort: 100
uiType: Strings
validate:
required: true
uiType: Group
validate: {}
- description: Number of consecutive failures required to determine the container
is not alive (liveness probe) or not ready (readiness probe).
jsonKey: failureThreshold
@@ -254,6 +282,14 @@
label: HttpGet
sort: 100
subParameters:
- description: The endpoint, relative to the port, to which the HTTP GET request
should be directed.
jsonKey: path
label: Path
sort: 100
uiType: Input
validate:
required: true
- description: The TCP socket within the container to which the HTTP GET request
should be directed.
jsonKey: port
@@ -283,14 +319,6 @@
required: true
uiType: Structs
validate: {}
- description: The endpoint, relative to the port, to which the HTTP GET request
should be directed.
jsonKey: path
label: Path
sort: 100
uiType: Input
validate:
required: true
uiType: Group
validate: {}
- description: Number of seconds after the container is started before the first
@@ -337,36 +365,14 @@
required: true
uiType: Group
validate: {}
- description: Number of seconds after which the probe times out.
jsonKey: timeoutSeconds
label: TimeoutSeconds
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: 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.
jsonKey: exec
label: Exec
sort: 100
subParameters:
- description: 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.
jsonKey: command
label: Command
sort: 100
uiType: Strings
validate:
required: true
uiType: Group
validate: {}
uiType: Group
validate: {}
- description: Specify image pull secrets for your service
jsonKey: imagePullSecrets
label: ImagePullSecrets
sort: 100
uiType: Strings
validate: {}
- description: Which port do you want customer traffic sent to
disable: true
jsonKey: port
@@ -376,12 +382,6 @@
validate:
defaultValue: 80
required: true
- description: Specify image pull secrets for your service
jsonKey: imagePullSecrets
label: ImagePullSecrets
sort: 100
uiType: Strings
validate: {}
- description: Declare volumes and volumeMounts
disable: true
jsonKey: volumes

View File

@@ -19,6 +19,7 @@ package usecase
import (
"context"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
@@ -37,6 +38,7 @@ type VelaQLUsecase interface {
type velaQLUsecaseImpl struct {
kubeClient client.Client
kubeConfig *rest.Config
dm discoverymapper.DiscoveryMapper
pd *packages.PackageDiscover
}
@@ -48,6 +50,11 @@ func NewVelaQLUsecase() VelaQLUsecase {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
kubeConfig, err := clients.GetKubeConfig()
if err != nil {
log.Logger.Fatalf("get kubeconfig failure %s", err.Error())
}
dm, err := clients.GetDiscoverMapper()
if err != nil {
log.Logger.Fatalf("get discover mapper failure %s", err.Error())
@@ -59,6 +66,7 @@ func NewVelaQLUsecase() VelaQLUsecase {
}
return &velaQLUsecaseImpl{
kubeClient: k8sClient,
kubeConfig: kubeConfig,
dm: dm,
pd: pd,
}
@@ -71,7 +79,7 @@ func (v *velaQLUsecaseImpl) QueryView(ctx context.Context, velaQL string) (*apis
return nil, bcode.ErrParseVelaQL
}
queryValue, err := velaql.NewViewHandler(v.kubeClient, v.dm, v.pd).QueryView(ctx, query)
queryValue, err := velaql.NewViewHandler(v.kubeClient, v.kubeConfig, v.dm, v.pd).QueryView(ctx, query)
if err != nil {
log.Logger.Errorf("fail to query the view %s", err.Error())
return nil, bcode.ErrViewQuery

View File

@@ -0,0 +1,111 @@
/*
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 usecase
import (
"context"
"errors"
"github.com/emicklei/go-restful/v3"
"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/policy/envbinding"
)
// WebhookUsecase webhook usecase
type WebhookUsecase interface {
HandleApplicationWebhook(ctx context.Context, token string, req *restful.Request) (*apisv1.ApplicationDeployResponse, error)
}
type webhookUsecaseImpl struct {
ds datastore.DataStore
applicationUsecase ApplicationUsecase
}
// NewWebhookUsecase new webhook usecase
func NewWebhookUsecase(ds datastore.DataStore,
applicationUsecase ApplicationUsecase,
) WebhookUsecase {
return &webhookUsecaseImpl{
ds: ds,
applicationUsecase: applicationUsecase,
}
}
func (c *webhookUsecaseImpl) HandleApplicationWebhook(ctx context.Context, token string, req *restful.Request) (*apisv1.ApplicationDeployResponse, error) {
webhookTrigger := &model.ApplicationTrigger{
Token: token,
}
if err := c.ds.Get(ctx, webhookTrigger); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrInvalidWebhookToken
}
return nil, err
}
app := &model.Application{
Name: webhookTrigger.AppPrimaryKey,
}
if err := c.ds.Get(ctx, app); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrApplicationNotExist
}
return nil, err
}
switch webhookTrigger.PayloadType {
case model.PayloadTypeCustom:
var webhookReq apisv1.HandleApplicationWebhookRequest
if err := req.ReadEntity(&webhookReq); err != nil {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
for comp, properties := range webhookReq.Upgrade {
component := &model.ApplicationComponent{
AppPrimaryKey: webhookTrigger.AppPrimaryKey,
Name: comp,
}
if err := c.ds.Get(ctx, component); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrApplicationComponetNotExist
}
return nil, err
}
merge, err := envbinding.MergeRawExtension(component.Properties.RawExtension(), properties.RawExtension())
if err != nil {
return nil, err
}
prop, err := model.NewJSONStructByStruct(merge)
if err != nil {
return nil, err
}
component.Properties = prop
if err := c.ds.Put(ctx, component); err != nil {
return nil, err
}
}
return c.applicationUsecase.Deploy(ctx, app, apisv1.ApplicationDeployRequest{
WorkflowName: webhookTrigger.WorkflowName,
Note: "triggered by webhook",
TriggerType: apisv1.TriggerTypeWebhook,
Force: true,
CodeInfo: webhookReq.CodeInfo,
})
default:
return nil, bcode.ErrInvalidWebhookPayloadType
}
}

View File

@@ -0,0 +1,148 @@
/*
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 usecase
import (
"bytes"
"context"
"encoding/json"
"net/http"
"github.com/emicklei/go-restful/v3"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"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/utils/apply"
)
var _ = Describe("Test application usecase function", func() {
var (
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
envUsecase *envUsecaseImpl
envBindingUsecase *envBindingUsecaseImpl
targetUsecase *targetUsecaseImpl
definitionUsecase *definitionUsecaseImpl
projectUsecase *projectUsecaseImpl
webhookUsecase *webhookUsecaseImpl
)
BeforeEach(func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "app-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
appUsecase = &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
apply: apply.NewAPIApplicator(k8sClient),
kubeClient: k8sClient,
envBindingUsecase: envBindingUsecase,
envUsecase: envUsecase,
definitionUsecase: definitionUsecase,
targetUsecase: targetUsecase,
projectUsecase: projectUsecase,
}
webhookUsecase = &webhookUsecaseImpl{
ds: ds,
applicationUsecase: appUsecase,
}
})
It("Test HandleApplicationWebhook function", func() {
_, err := targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook"})
Expect(err).Should(BeNil())
_, err = projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "webhook-dev", Namespace: "webhook-dev", Targets: []string{"dev-target-webhook"}, Project: "project-webhook"})
Expect(err).Should(BeNil())
Expect(err).Should(BeNil())
req := apisv1.CreateApplicationRequest{
Name: "test-app-webhook",
Project: "project-webhook",
Description: "this is a test app",
EnvBinding: []*apisv1.EnvBinding{{
Name: "webhook-dev",
}},
Component: &apisv1.CreateComponentRequest{
Name: "component-name-webhook",
ComponentType: "webservice",
},
}
_, err = appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-webhook")
Expect(err).Should(BeNil())
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), "invalid-token", nil)
Expect(err).Should(Equal(bcode.ErrInvalidWebhookToken))
triggers, err := appUsecase.ListApplicationTriggers(context.TODO(), appModel)
Expect(err).Should(BeNil())
reqBody := apisv1.HandleApplicationWebhookRequest{
Upgrade: map[string]*model.JSONStruct{
"component-name-webhook": {
"image": "test-image",
"test1": map[string]string{
"test2": "test3",
},
},
},
CodeInfo: &model.CodeInfo{
Commit: "test-commit",
Branch: "test-branch",
User: "test-user",
},
}
body, err := json.Marshal(reqBody)
Expect(err).Should(BeNil())
httpreq, err := http.NewRequest("post", "/", bytes.NewBuffer(body))
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
Expect(err).Should(BeNil())
res, err := webhookUsecase.HandleApplicationWebhook(context.TODO(), triggers[0].Token, restful.NewRequest(httpreq))
Expect(err).Should(BeNil())
comp, err := appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("test-image"))
Expect((*comp.Properties)["test1"]).Should(Equal(map[string]interface{}{
"test2": "test3",
}))
revision := &model.ApplicationRevision{
AppPrimaryKey: "test-app-webhook",
Version: res.Version,
}
err = webhookUsecase.ds.Get(context.TODO(), revision)
Expect(err).Should(BeNil())
Expect(revision.CodeInfo.Commit).Should(Equal("test-commit"))
Expect(revision.CodeInfo.Branch).Should(Equal("test-branch"))
Expect(revision.CodeInfo.User).Should(Equal("test-user"))
})
})

View File

@@ -65,7 +65,7 @@ type WorkflowUsecase interface {
}
// NewWorkflowUsecase new workflow usecase
func NewWorkflowUsecase(ds datastore.DataStore) WorkflowUsecase {
func NewWorkflowUsecase(ds datastore.DataStore, envUsecase EnvUsecase) WorkflowUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
@@ -74,6 +74,7 @@ func NewWorkflowUsecase(ds datastore.DataStore) WorkflowUsecase {
ds: ds,
kubeClient: kubecli,
apply: apply.NewAPIApplicator(kubecli),
envUsecase: envUsecase,
}
}
@@ -81,6 +82,7 @@ type workflowUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
apply apply.Applicator
envUsecase EnvUsecase
}
// DeleteWorkflow delete application workflow
@@ -198,72 +200,38 @@ func (w *workflowUsecaseImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
return w.DetailWorkflow(ctx, workflow)
}
func (w *workflowUsecaseImpl) UpdateWorkflow(ctx context.Context, workflow *model.Workflow, req apisv1.UpdateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error) {
var steps []model.WorkflowStep
for _, step := range req.Steps {
properties, err := model.NewJSONStructByString(step.Properties)
if err != nil {
log.Logger.Errorf("parse trait properties failire %w", err)
return nil, bcode.ErrInvalidProperties
}
steps = append(steps, model.WorkflowStep{
Name: step.Name,
Alias: step.Alias,
Description: step.Description,
DependsOn: step.DependsOn,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
Properties: properties,
})
}
// updateWorkflowSteps will update workflow with new steps
func updateWorkflowSteps(ctx context.Context, ds datastore.DataStore, workflow *model.Workflow, steps []model.WorkflowStep) error {
workflow.Steps = steps
return ds.Put(ctx, workflow)
}
func (w *workflowUsecaseImpl) UpdateWorkflow(ctx context.Context, workflow *model.Workflow, req apisv1.UpdateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error) {
modeSteps, err := convertAPIStep2ModelStep(req.Steps)
if err != nil {
return nil, err
}
workflow.Description = req.Description
// It is allowed to set multiple workflows as default, and only one takes effect.
workflow.Default = &req.Default
if err := w.ds.Put(ctx, workflow); err != nil {
if req.Default != nil {
workflow.Default = req.Default
}
if err := updateWorkflowSteps(ctx, w.ds, workflow, modeSteps); err != nil {
return nil, err
}
return w.DetailWorkflow(ctx, workflow)
}
func converWorkflowBase(workflow *model.Workflow) apisv1.WorkflowBase {
var steps []apisv1.WorkflowStep
for _, step := range workflow.Steps {
steps = append(steps, convertFromWorkflowStepModel(step))
}
return apisv1.WorkflowBase{
Name: workflow.Name,
Alias: workflow.Alias,
Description: workflow.Description,
Default: convertBool(workflow.Default),
EnvName: workflow.EnvName,
CreateTime: workflow.CreateTime,
UpdateTime: workflow.UpdateTime,
Steps: steps,
}
}
// DetailWorkflow detail workflow
func (w *workflowUsecaseImpl) DetailWorkflow(ctx context.Context, workflow *model.Workflow) (*apisv1.DetailWorkflowResponse, error) {
return &apisv1.DetailWorkflowResponse{
WorkflowBase: converWorkflowBase(workflow),
WorkflowBase: convertWorkflowBase(workflow),
}, nil
}
// GetWorkflow get workflow model
func (w *workflowUsecaseImpl) GetWorkflow(ctx context.Context, app *model.Application, workflowName string) (*model.Workflow, error) {
var workflow = model.Workflow{
Name: workflowName,
AppPrimaryKey: app.PrimaryKey(),
}
if err := w.ds.Get(ctx, &workflow); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrWorkflowNotExist
}
return nil, err
}
return &workflow, nil
return getWorkflowForApp(ctx, w.ds, app, workflowName)
}
// ListApplicationWorkflow list application workflows
@@ -278,7 +246,7 @@ func (w *workflowUsecaseImpl) ListApplicationWorkflow(ctx context.Context, app *
var list []*apisv1.WorkflowBase
for _, workflow := range workflows {
wm := workflow.(*model.Workflow)
base := converWorkflowBase(wm)
base := convertWorkflowBase(wm)
list = append(list, &base)
}
return list, nil
@@ -387,7 +355,7 @@ func (w *workflowUsecaseImpl) SyncWorkflowRecord(ctx context.Context) error {
klog.ErrorS(err, "failed to get workflow", "app name", record.AppPrimaryKey, "workflow name", record.WorkflowName, "record name", record.Name)
continue
}
appName := convertAppName(record.AppPrimaryKey, workflow.EnvName)
appName := record.AppPrimaryKey
if err := w.kubeClient.Get(ctx, types.NamespacedName{
Name: appName,
Namespace: record.Namespace,
@@ -519,7 +487,7 @@ func (w *workflowUsecaseImpl) CreateWorkflowRecord(ctx context.Context, appModel
AppPrimaryKey: appModel.PrimaryKey(),
RevisionPrimaryKey: app.Annotations[oam.AnnotationDeployVersion],
Name: app.Annotations[oam.AnnotationPublishVersion],
Namespace: appModel.Namespace,
Namespace: app.Namespace,
Finished: "false",
StartTime: time.Now().Time,
Steps: steps,
@@ -635,8 +603,9 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode
var revision = model.ApplicationRevision{
AppPrimaryKey: appModel.Name,
Status: model.RevisionStatusComplete,
WorkflowName: workflow.Name,
EnvName: workflow.EnvName,
}
revisions, err := w.ds.List(ctx, &revision, &datastore.ListOptions{
Page: 1,
PageSize: 1,
@@ -649,6 +618,7 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode
return bcode.ErrApplicationNoReadyRevision
}
revisionVersion = revisions[0].Index()["version"]
log.Logger.Infof("select lastest complete revision %s", revisions[0].Index()["version"])
}
var record = &model.WorkflowRecord{
@@ -719,7 +689,11 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode
func (w *workflowUsecaseImpl) checkRecordRunning(ctx context.Context, appModel *model.Application, envName string) (*v1beta1.Application, error) {
oamApp := &v1beta1.Application{}
if err := w.kubeClient.Get(ctx, types.NamespacedName{Name: convertAppName(appModel.Name, envName), Namespace: appModel.Namespace}, oamApp); err != nil {
env, err := w.envUsecase.GetEnv(ctx, envName)
if err != nil {
return nil, err
}
if err := w.kubeClient.Get(ctx, types.NamespacedName{Name: appModel.Name, Namespace: env.Namespace}, oamApp); err != nil {
return nil, err
}
if oamApp.Status.Workflow != nil && !oamApp.Status.Workflow.Suspend && !oamApp.Status.Workflow.Terminated && !oamApp.Status.Workflow.Finished {

View File

@@ -0,0 +1,171 @@
/*
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 usecase
import (
"context"
"errors"
"fmt"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"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"
"github.com/oam-dev/kubevela/pkg/oam/util"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
)
// UpdateEnvWorkflow will update env workflow internally
func UpdateEnvWorkflow(ctx context.Context, kubeClient client.Client, ds datastore.DataStore, app *model.Application, env *model.Env) error {
// The existing step configuration should be maintained and the delivery target steps should be automatically updated.
envSteps := GenEnvWorkflowSteps(ctx, kubeClient, ds, env, app)
workflow, err := getWorkflowForApp(ctx, ds, app, convertWorkflowName(env.Name))
if err != nil {
// no workflow exist mean no need to update
if errors.Is(err, bcode.ErrWorkflowNotExist) {
return nil
}
return err
}
var envStepNames = env.Targets
var workflowStepNames []string
for _, step := range workflow.Steps {
if isEnvStepType(step.Type) {
workflowStepNames = append(workflowStepNames, step.Name)
}
}
var filteredSteps []apisv1.WorkflowStep
_, readyToDeleteSteps, readyToAddSteps := compareSlices(workflowStepNames, envStepNames)
for _, step := range workflow.Steps {
if isEnvStepType(step.Type) && utils.StringsContain(readyToDeleteSteps, step.Name) {
continue
}
filteredSteps = append(filteredSteps, convertFromWorkflowStepModel(step))
}
for _, step := range envSteps {
if isEnvStepType(step.Type) && utils.StringsContain(readyToAddSteps, step.Name) {
filteredSteps = append(filteredSteps, step)
}
}
modelSteps, err := convertAPIStep2ModelStep(filteredSteps)
if err != nil {
return err
}
err = updateWorkflowSteps(ctx, ds, workflow, modelSteps)
if err != nil {
return err
}
return nil
}
// GetComponentDefinition will get componentDefinition by kube client
func GetComponentDefinition(ctx context.Context, kubeClient client.Client, name string) (*v1beta1.ComponentDefinition, error) {
var componentDefinition v1beta1.ComponentDefinition
if err := kubeClient.Get(ctx, k8stypes.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: name}, &componentDefinition); err != nil {
return nil, err
}
return &componentDefinition, nil
}
// GetSuitableDeployWay will get a workflow deploy strategy for workflow step
func GetSuitableDeployWay(ctx context.Context, kubeClient client.Client, ds datastore.DataStore, app *model.Application) string {
components, err := ds.List(ctx, &model.ApplicationComponent{AppPrimaryKey: app.PrimaryKey()}, &datastore.ListOptions{PageSize: 1, Page: 1})
if err != nil {
log.Logger.Errorf("list application component list failure %s", err.Error())
}
if len(components) > 0 {
component := components[0].(*model.ApplicationComponent)
definition, err := GetComponentDefinition(ctx, kubeClient, component.Type)
if err != nil {
log.Logger.Errorf("get component definition %s failure %s", component.Type, err.Error())
// using Deploy2Env by default
}
if definition != nil {
if definition.Spec.Workload.Type == TerraformWorkfloadType {
return DeployCloudResource
}
if definition.Spec.Workload.Definition.Kind == TerraformWorkfloadKind {
return DeployCloudResource
}
}
}
return Deploy2Env
}
// GenEnvWorkflowSteps will generate workflow steps for an env and application
func GenEnvWorkflowSteps(ctx context.Context, kubeClient client.Client, ds datastore.DataStore, env *model.Env, app *model.Application) []apisv1.WorkflowStep {
var workflowSteps []v1beta1.WorkflowStep
for _, targetName := range env.Targets {
step := v1beta1.WorkflowStep{
Name: genPolicyEnvName(targetName),
Type: GetSuitableDeployWay(ctx, kubeClient, ds, app),
Properties: util.Object2RawExtension(map[string]string{
"policy": genPolicyName(env.Name),
"env": genPolicyEnvName(targetName),
}),
}
workflowSteps = append(workflowSteps, step)
}
var steps []apisv1.WorkflowStep
for _, step := range workflowSteps {
var propertyStr string
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", utils2.Sanitize(app.Name), utils2.Sanitize(step.Name), err.Error())
continue
}
propertyStr = properties.JSON()
}
steps = append(steps, apisv1.WorkflowStep{
Name: step.Name,
Type: step.Type,
Alias: fmt.Sprintf("Deploy To %s", step.Name),
Description: fmt.Sprintf("deploy app to delivery target %s", step.Name),
DependsOn: step.DependsOn,
Properties: propertyStr,
Inputs: step.Inputs,
Outputs: step.Outputs,
})
}
return steps
}
func getWorkflowForApp(ctx context.Context, ds datastore.DataStore, app *model.Application, workflowName string) (*model.Workflow, error) {
var workflow = model.Workflow{
Name: workflowName,
AppPrimaryKey: app.PrimaryKey(),
}
if err := ds.Get(ctx, &workflow); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrWorkflowNotExist
}
return nil, err
}
return &workflow, nil
}

View File

@@ -20,6 +20,8 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/google/go-cmp/cmp"
. "github.com/onsi/ginkgo"
@@ -32,6 +34,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/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/oam"
@@ -44,19 +47,32 @@ var _ = Describe("Test workflow usecase functions", func() {
workflowUsecase *workflowUsecaseImpl
appUsecase *applicationUsecaseImpl
projectUsecase *projectUsecaseImpl
envUsecase *envUsecaseImpl
testProject = "workflow-project"
ds datastore.DataStore
)
BeforeEach(func() {
workflowUsecase = &workflowUsecaseImpl{ds: ds, kubeClient: k8sClient, apply: apply.NewAPIApplicator(k8sClient)}
projectUsecase = &projectUsecaseImpl{ds: ds, kubeClient: k8sClient}
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "workflow-test-" + strconv.FormatInt(time.Now().UnixNano(), 10)})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase = &projectUsecaseImpl{ds: ds}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{
ds: ds,
kubeClient: k8sClient,
apply: apply.NewAPIApplicator(k8sClient),
envUsecase: envUsecase}
appUsecase = &applicationUsecaseImpl{ds: ds, kubeClient: k8sClient,
apply: apply.NewAPIApplicator(k8sClient),
projectUsecase: projectUsecase,
envUsecase: envUsecase,
envBindingUsecase: &envBindingUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
envUsecase: envUsecase,
}}
})
It("Test CreateWorkflow function", func() {
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: testProject})
@@ -66,9 +82,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Project: testProject,
Description: "this is a test app",
EnvBinding: []*apisv1.EnvBinding{{
Name: "dev",
Description: "dev env",
TargetNames: []string{"dev-target"},
Name: "dev",
}},
}
_, err = appUsecase.CreateApplication(context.TODO(), reqApp)
@@ -80,8 +94,7 @@ var _ = Describe("Test workflow usecase functions", func() {
}
base, err := workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
@@ -93,8 +106,7 @@ var _ = Describe("Test workflow usecase functions", func() {
}
base, err = workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req2)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req2.Description)).Should(BeEmpty())
@@ -117,23 +129,19 @@ var _ = Describe("Test workflow usecase functions", func() {
Default: &defaultW,
}
base, err = workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
})
It("Test GetApplicationDefaultWorkflow function", func() {
By("Test GetApplicationDefaultWorkflow function")
workflow, err := workflowUsecase.GetApplicationDefaultWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
})
Expect(err).Should(BeNil())
Expect(workflow).ShouldNot(BeNil())
})
It("Test ListWorkflowRecords function", func() {
By("Test ListWorkflowRecords function")
By("create some workflow records to test list workflow records")
raw, err := yaml.YAMLToJSON([]byte(yamlStr))
Expect(err).Should(BeNil())
@@ -141,17 +149,15 @@ var _ = Describe("Test workflow usecase functions", func() {
err = json.Unmarshal(raw, app)
Expect(err).Should(BeNil())
app.Annotations[oam.AnnotationWorkflowName] = "test-workflow-2"
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
for i := 0; i < 3; i++ {
app.Annotations[oam.AnnotationPublishVersion] = fmt.Sprintf("list-workflow-name-%d", i)
app.Status.Workflow.AppRevision = fmt.Sprintf("list-workflow-name-%d", i)
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
}
@@ -159,26 +165,22 @@ var _ = Describe("Test workflow usecase functions", func() {
resp, err := workflowUsecase.ListWorkflowRecords(context.TODO(), workflow, 0, 10)
Expect(err).Should(BeNil())
Expect(resp.Total).Should(Equal(int64(3)))
})
It("Test DetailWorkflowRecord function", func() {
By("create one workflow record to test detail workflow record")
raw, err := yaml.YAMLToJSON([]byte(yamlStr))
raw, err = yaml.YAMLToJSON([]byte(yamlStr))
Expect(err).Should(BeNil())
app := &v1beta1.Application{}
app = &v1beta1.Application{}
err = json.Unmarshal(raw, app)
Expect(err).Should(BeNil())
app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-123"
app.Status.Workflow.AppRevision = "test-workflow-2-123"
app.Annotations[oam.AnnotationDeployVersion] = "1234"
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -199,13 +201,11 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
Expect(detail.WorkflowRecord.Name).Should(Equal("test-workflow-2-123"))
Expect(detail.DeployUser).Should(Equal("test-user"))
})
It("Test SyncWorkflowRecord function", func() {
By("create one workflow record to test sync status from application")
raw, err := yaml.YAMLToJSON([]byte(yamlStr))
raw, err = yaml.YAMLToJSON([]byte(yamlStr))
Expect(err).Should(BeNil())
app := &v1beta1.Application{}
app = &v1beta1.Application{}
err = json.Unmarshal(raw, app)
Expect(err).Should(BeNil())
app.Status.Workflow.Finished = false
@@ -213,19 +213,17 @@ var _ = Describe("Test workflow usecase functions", func() {
app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-233"
app.Status.Workflow.AppRevision = "test-workflow-2-233"
app.Annotations[oam.AnnotationDeployVersion] = "4321"
workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
By("create one revision to test sync workflow record")
var revision = &model.ApplicationRevision{
revision = &model.ApplicationRevision{
AppPrimaryKey: appName,
Version: "4321",
Status: model.RevisionStatusInit,
@@ -246,8 +244,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
workflow, err = workflowUsecase.GetWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
By("check the record")
@@ -270,8 +267,7 @@ var _ = Describe("Test workflow usecase functions", func() {
app.Status.Workflow.AppRevision = "test-workflow-2-111"
app.Annotations[oam.AnnotationDeployVersion] = "1111"
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -290,7 +286,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
cr := &appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Name: "record-app-workflow-dev-test-workflow-2-111",
Name: "record-app-workflow-test-workflow-2-111",
Namespace: "default",
Labels: map[string]string{"vela.io/wf-revision": "test-workflow-2-111"},
},
@@ -324,12 +320,11 @@ var _ = Describe("Test workflow usecase functions", func() {
})
}
app, err := createTestSuspendApp(ctx, "record-app", "dev", "revision-123", "test-workflow", "test-record-3", workflowUsecase.kubeClient)
app, err := createTestSuspendApp(ctx, "record-app", "default", "revision-123", "test-workflow", "test-record-3", workflowUsecase.kubeClient)
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(ctx, &model.Application{
Name: "record-app",
Namespace: "default",
Name: "record-app",
}, app, &model.Workflow{Name: "test-workflow"})
Expect(err).Should(BeNil())
@@ -346,6 +341,8 @@ var _ = Describe("Test workflow usecase functions", func() {
It("Test ResumeRecord function", func() {
ctx := context.TODO()
_, err := envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "resume"})
Expect(err).Should(BeNil())
ResumeWorkflow := "resume-workflow"
req := apisv1.CreateWorkflowRequest{
Name: ResumeWorkflow,
@@ -354,8 +351,7 @@ var _ = Describe("Test workflow usecase functions", func() {
}
base, err := workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
@@ -364,8 +360,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, &model.Workflow{Name: ResumeWorkflow})
Expect(err).Should(BeNil())
@@ -377,8 +372,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
err = workflowUsecase.ResumeRecord(ctx, &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, &model.Workflow{Name: ResumeWorkflow, EnvName: "resume"}, "workflow-resume-1")
Expect(err).Should(BeNil())
@@ -390,6 +384,8 @@ var _ = Describe("Test workflow usecase functions", func() {
It("Test TerminateRecord function", func() {
ctx := context.TODO()
_, err := envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "terminate"})
Expect(err).Should(BeNil())
workflowName := "terminate-workflow"
req := apisv1.CreateWorkflowRequest{
Name: workflowName,
@@ -398,8 +394,7 @@ var _ = Describe("Test workflow usecase functions", func() {
}
workflow := &model.Workflow{Name: workflowName, EnvName: "terminate"}
base, err := workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
@@ -408,8 +403,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -421,8 +415,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
err = workflowUsecase.TerminateRecord(ctx, &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, workflow, "test-workflow-2-1")
Expect(err).Should(BeNil())
@@ -433,7 +426,8 @@ var _ = Describe("Test workflow usecase functions", func() {
It("Test RollbackRecord function", func() {
ctx := context.TODO()
_, err := envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "rollback"})
Expect(err).Should(BeNil())
workflowName := "rollback-workflow"
req := apisv1.CreateWorkflowRequest{
Name: workflowName,
@@ -442,8 +436,7 @@ var _ = Describe("Test workflow usecase functions", func() {
}
workflow := &model.Workflow{Name: workflowName, EnvName: "rollback"}
base, err := workflowUsecase.CreateOrUpdateWorkflow(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty())
@@ -452,8 +445,7 @@ var _ = Describe("Test workflow usecase functions", func() {
Expect(err).Should(BeNil())
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -461,6 +453,8 @@ var _ = Describe("Test workflow usecase functions", func() {
AppPrimaryKey: appName,
Version: "revision-rollback1",
Status: model.RevisionStatusRunning,
WorkflowName: workflow.Name,
EnvName: "rollback",
})
Expect(err).Should(BeNil())
err = workflowUsecase.createTestApplicationRevision(ctx, &model.ApplicationRevision{
@@ -468,12 +462,13 @@ var _ = Describe("Test workflow usecase functions", func() {
Version: "revision-rollback0",
ApplyAppConfig: `{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"annotations":{"app.oam.dev/workflowName":"test-workflow-2-2","app.oam.dev/deployVersion":"revision-rollback1","vela.io/publish-version":"workflow-rollback1"},"name":"first-vela-app","namespace":"default"},"spec":{"components":[{"name":"express-server","properties":{"image":"crccheck/hello-world","port":8000},"traits":[{"properties":{"domain":"testsvc.example.com","http":{"/":8000}},"type":"ingress-1-20"}],"type":"webservice"}]}}`,
Status: model.RevisionStatusComplete,
WorkflowName: workflow.Name,
EnvName: "rollback",
})
Expect(err).Should(BeNil())
err = workflowUsecase.RollbackRecord(ctx, &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, workflow, "test-workflow-2-2", "revision-rollback0")
Expect(err).Should(BeNil())
@@ -489,14 +484,12 @@ var _ = Describe("Test workflow usecase functions", func() {
app.Annotations[oam.AnnotationPublishVersion] = "workflow-rollback-2"
app.Status.Workflow.AppRevision = "workflow-rollback-2"
err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
err = workflowUsecase.RollbackRecord(ctx, &model.Application{
Name: appName,
Namespace: "default",
Name: appName,
}, workflow, "workflow-rollback-2", "")
Expect(err).Should(BeNil())
@@ -527,7 +520,7 @@ metadata:
app.oam.dev/deployVersion: "1234"
app.oam.dev/publishVersion: "test-workflow-name-111"
app.oam.dev/appName: "app-workflow"
name: app-workflow-dev
name: app-workflow
namespace: default
spec:
components:

View File

@@ -61,10 +61,10 @@ var ErrApplicationNotEnv = NewBcode(404, 10013, "application not set env binding
// ErrApplicationEnvExist application env is exist
var ErrApplicationEnvExist = NewBcode(400, 10014, "application env is exist")
// ErrTraitNotExist trait is not exist
// ErrTraitNotExist trait is not exist
var ErrTraitNotExist = NewBcode(400, 10015, "trait is not exist")
// ErrTraitAlreadyExist trait is already exist
// ErrTraitAlreadyExist trait is already exist
var ErrTraitAlreadyExist = NewBcode(400, 10016, "trait is already exist")
// ErrApplicationNoReadyRevision application not have ready revision
@@ -73,8 +73,17 @@ var ErrApplicationNoReadyRevision = NewBcode(400, 10017, "application not have r
// ErrApplicationRevisionNotExist application revision is not exist
var ErrApplicationRevisionNotExist = NewBcode(404, 10018, "application revision is not exist")
// ErrApplicationRefusedDelete The application cannot be deleted because it has been deployed
// ErrApplicationRefusedDelete means the application cannot be deleted because it has been deployed
var ErrApplicationRefusedDelete = NewBcode(400, 10019, "The application cannot be deleted because it has been deployed")
// ErrApplicationEnvRefusedDelete The application env cannot be deleted because it has been deployed
// ErrApplicationEnvRefusedDelete means he application env cannot be deleted because it has been deployed
var ErrApplicationEnvRefusedDelete = NewBcode(400, 10020, "The application envbinding cannot be deleted because it has been deployed")
// ErrInvalidWebhookToken means the webhook token is invalid
var ErrInvalidWebhookToken = NewBcode(400, 10021, "Invalid webhook token")
// ErrInvalidWebhookPayloadType means the webhook payload type is invalid
var ErrInvalidWebhookPayloadType = NewBcode(400, 10022, "Invalid webhook payload type")
// ErrInvalidWebhookPayloadBody means the webhook payload body is invalid
var ErrInvalidWebhookPayloadBody = NewBcode(400, 10023, "Invalid webhook payload body")

View File

@@ -17,10 +17,10 @@ limitations under the License.
package bcode
// ErrProjectIsExist project name is exist
var ErrProjectIsExist = NewBcode(400, 30001, "project name is exist")
var ErrProjectIsExist = NewBcode(400, 30001, "project name already exists")
// ErrProjectIsNotExist project is not exist
var ErrProjectIsNotExist = NewBcode(404, 30002, "project is not exist")
var ErrProjectIsNotExist = NewBcode(404, 30002, "project is not existed")
// ErrProjectNamespaceFail project bind namespace failure
var ErrProjectNamespaceFail = NewBcode(400, 30003, "project bind namespace failure")

View File

@@ -0,0 +1,29 @@
/*
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 bcode
// ErrTargetExist Target is exist
var ErrTargetExist = NewBcode(400, 80001, "target is exist")
// ErrTargetNotExist Target is not exist
var ErrTargetNotExist = NewBcode(404, 80002, "target is not exist")
// ErrTargetInUseCantDeleted Target being used
var ErrTargetInUseCantDeleted = NewBcode(404, 80003, "target in use, can't be deleted")
// ErrTargetNamespaceAlreadyBound indicates the namespace already belongs to other target, one namespace can only belong to one target
var ErrTargetNamespaceAlreadyBound = NewBcode(400, 80004, "the namespace specified already belongs to other target")

View File

@@ -0,0 +1,35 @@
/*
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 bcode
// ErrEnvAlreadyExists Env name is existed
var ErrEnvAlreadyExists = NewBcode(400, 11001, "env name already exists")
// ErrEnvNotExisted means env is not existed
var ErrEnvNotExisted = NewBcode(404, 11002, "env is not existed")
// ErrEnvNamespaceFail env binds namespace failure
var ErrEnvNamespaceFail = NewBcode(400, 11003, "env bind namespace failure")
// ErrEnvNamespaceAlreadyBound indicates the namespace already belongs to other env
var ErrEnvNamespaceAlreadyBound = NewBcode(400, 11004, "the namespace specified already belongs to other env")
// ErrDeleteEnvButAppExist reports an error when delete an Env but still has apps inside
var ErrDeleteEnvButAppExist = NewBcode(400, 11005, "env can't be deleted as app existed inside")
// ErrEnvTargetConflict in one project, one target can only belong to one env
var ErrEnvTargetConflict = NewBcode(400, 11006, "in one project, one target can only belong to one env.")

View File

@@ -20,7 +20,7 @@ import (
"errors"
"fmt"
restful "github.com/emicklei/go-restful/v3"
"github.com/emicklei/go-restful/v3"
"github.com/go-playground/validator/v10"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
@@ -28,6 +28,10 @@ import (
"github.com/oam-dev/kubevela/pkg/utils"
)
// Business Code of VelaUX contains 5 digits, the first 3 digits should be reversed and indicates the category of concept
// the last two digits indicates the error number
// For example, business code 11001 should split to 110 and 01, it means the code belongs to the 011 category env, and it's the 01 number error.
// ErrServer an unexpected mistake.
var ErrServer = NewBcode(500, 500, "The service has lapsed.")

View File

@@ -1,26 +0,0 @@
/*
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 bcode
// ErrDeliveryTargetExist deliveryTarget is exist
var ErrDeliveryTargetExist = NewBcode(400, 80001, "deliveryTarget is exist")
// ErrDeliveryTargetNotExist deliveryTarget is not exist
var ErrDeliveryTargetNotExist = NewBcode(404, 80002, "deliveryTarget is not exist")
// ErrDeliveryTargetInUseCantDeleted deliveryTarget being used
var ErrDeliveryTargetInUseCantDeleted = NewBcode(404, 80003, "deliveryTarget in use, can't be deleted")

View File

@@ -0,0 +1,92 @@
/*
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 utils
import (
"bytes"
"net"
"net/http"
"strings"
)
// ClientIP get client ip
func ClientIP(r *http.Request) string {
xForwardedFor := r.Header.Get("X-Forwarded-For")
ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
if ip != "" {
return ip
}
ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
if ip != "" {
return ip
}
if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
return ip
}
return ""
}
// ResponseCapture capture response and get response info
type ResponseCapture struct {
http.ResponseWriter
wroteHeader bool
status int
body *bytes.Buffer
}
// NewResponseCapture new response capture
func NewResponseCapture(w http.ResponseWriter) *ResponseCapture {
return &ResponseCapture{
ResponseWriter: w,
wroteHeader: false,
body: new(bytes.Buffer),
}
}
// Header return response writer header
func (c ResponseCapture) Header() http.Header {
return c.ResponseWriter.Header()
}
// Write write data to response writer and body
func (c ResponseCapture) Write(data []byte) (int, error) {
if !c.wroteHeader {
c.WriteHeader(http.StatusOK)
}
c.body.Write(data)
return c.ResponseWriter.Write(data)
}
// WriteHeader write header to response writer
func (c *ResponseCapture) WriteHeader(statusCode int) {
c.status = statusCode
c.wroteHeader = true
c.ResponseWriter.WriteHeader(statusCode)
}
// Bytes return response body bytes
func (c ResponseCapture) Bytes() []byte {
return c.body.Bytes()
}
// StatusCode return status code
func (c ResponseCapture) StatusCode() int {
return c.status
}

View File

@@ -20,7 +20,6 @@ import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
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"
@@ -122,13 +121,17 @@ func (s *addonWebService) listAddons(req *restful.Request, res *restful.Response
return
}
var addons []*pkgaddon.Meta
var addons []*apis.AddonInfo
for _, d := range detailAddons {
addons = append(addons, &d.Meta)
addons = append(addons, &apis.AddonInfo{Meta: &d.Meta, RegistryName: d.RegistryName})
}
err = res.WriteEntity(apis.ListAddonResponse{Addons: addons, Message: err.Error()})
var message string
if err != nil {
message = err.Error()
}
err = res.WriteEntity(apis.ListAddonResponse{Addons: addons, Message: message})
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -234,7 +237,7 @@ type enabledAddonWebService struct {
func (s *enabledAddonWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/enabled-addon").
ws.Path(versionPrefix+"/enabled_addon").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for addon management")

View File

@@ -22,7 +22,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
@@ -103,6 +103,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(200, "", apis.ApplicationBase{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationBase{}))
ws.Route(ws.GET("/{name}/statistics").To(c.applicationStatistics).
Doc("detail one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -122,6 +123,25 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationBase{}))
ws.Route(ws.POST("/{name}/triggers").To(c.createApplicationTrigger).
Doc("create one application trigger").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationTriggerRequest{}).
Returns(200, "", apis.ApplicationTriggerBase{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationTriggerBase{}))
ws.Route(ws.GET("/{name}/triggers").To(c.listApplicationTriggers).
Doc("list application triggers").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Returns(200, "", apis.ListApplicationTriggerResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes([]*apis.ApplicationTriggerBase{}))
ws.Route(ws.POST("/{name}/template").To(c.publishApplicationTemplate).
Doc("create one application template").
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -306,7 +326,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationEnvRequest{}).
Reads(apis.CreateApplicationEnvbindingRequest{}).
Returns(200, "", apis.EnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
@@ -318,7 +338,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the envBinding ").DataType("string")).
Reads(apis.PutApplicationEnvRequest{}).
Reads(apis.PutApplicationEnvBindingRequest{}).
Returns(200, "", apis.EnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EnvBinding{}))
@@ -504,8 +524,9 @@ func (c *applicationWebService) createApplication(req *restful.Request, res *res
}
func (c *applicationWebService) listApplications(req *restful.Request, res *restful.Response) {
apps, err := c.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicatioOptions{
apps, err := c.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicationOptions{
Project: req.QueryParameter("project"),
Env: req.QueryParameter("env"),
TargetName: req.QueryParameter("targetName"),
Query: req.QueryParameter("query"),
})
@@ -532,6 +553,37 @@ func (c *applicationWebService) detailApplication(req *restful.Request, res *res
}
}
func (c *applicationWebService) createApplicationTrigger(req *restful.Request, res *restful.Response) {
var createReq apis.CreateApplicationTriggerRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
base, err := c.applicationUsecase.CreateApplicationTrigger(req.Request.Context(), app, createReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) listApplicationTriggers(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
triggers, err := c.applicationUsecase.ListApplicationTriggers(req.Request.Context(), app)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.ListApplicationTriggerResponse{Triggers: triggers}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) publishApplicationTemplate(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
base, err := c.applicationUsecase.PublishApplicationTemplate(req.Request.Context(), app)
@@ -872,7 +924,7 @@ func (c *applicationWebService) detailApplicationRevision(req *restful.Request,
func (c *applicationWebService) updateApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var updateReq apis.PutApplicationEnvRequest
var updateReq apis.PutApplicationEnvBindingRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
@@ -908,7 +960,7 @@ func (c *applicationWebService) listApplicationEnvs(req *restful.Request, res *r
func (c *applicationWebService) createApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var createReq apis.CreateApplicationEnvRequest
var createReq apis.CreateApplicationEnvbindingRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return

View File

@@ -1,216 +0,0 @@
/*
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 webservice
import (
"context"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"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/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
// NewDeliveryTargetWebService new deliveryTarget webservice
func NewDeliveryTargetWebService(deliveryTargetUsecase usecase.DeliveryTargetUsecase, applicationUsecase usecase.ApplicationUsecase) WebService {
return &DeliveryTargetWebService{
deliveryTargetUsecase: deliveryTargetUsecase,
applicationUsecase: applicationUsecase,
}
}
// DeliveryTargetWebService delivery target web service
type DeliveryTargetWebService struct {
deliveryTargetUsecase usecase.DeliveryTargetUsecase
applicationUsecase usecase.ApplicationUsecase
}
// GetWebService get web service
func (dt *DeliveryTargetWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/targets").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for deliveryTarget manage")
tags := []string{"deliveryTarget"}
ws.Route(ws.GET("/").To(dt.listDeliveryTargets).
Doc("list deliveryTarget").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("project", "Query the target belong to project").DataType("string")).
Param(ws.QueryParameter("page", "Page for paging").DataType("integer")).
Param(ws.QueryParameter("pageSize", "PageSize for paging").DataType("integer")).
Returns(200, "", apis.ListTargetResponse{}).
Writes(apis.ListTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.POST("/").To(dt.createDeliveryTarget).
Doc("create deliveryTarget").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateDeliveryTargetRequest{}).
Returns(200, "create success", apis.DetailDeliveryTargetResponse{}).
Returns(400, "create failure", bcode.Bcode{}).
Writes(apis.DetailDeliveryTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}").To(dt.detailDeliveryTarget).
Doc("detail deliveryTarget").
Param(ws.PathParameter("name", "identifier of the deliveryTarget.").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.deliveryTargetCheckFilter).
Returns(200, "create success", apis.DetailDeliveryTargetResponse{}).
Writes(apis.DetailDeliveryTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.PUT("/{name}").To(dt.updateDeliveryTarget).
Doc("update application DeliveryTarget config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.deliveryTargetCheckFilter).
Param(ws.PathParameter("name", "identifier of the deliveryTarget").DataType("string")).
Reads(apis.UpdateDeliveryTargetRequest{}).
Returns(200, "", apis.DetailDeliveryTargetResponse{}).
Writes(apis.DetailDeliveryTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.DELETE("/{name}").To(dt.deleteDeliveryTarget).
Doc("deletet DeliveryTarget").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.deliveryTargetCheckFilter).
Param(ws.PathParameter("name", "identifier of the deliveryTarget").DataType("string")).
Returns(200, "", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}).Do(returns200, returns500))
return ws
}
func (dt *DeliveryTargetWebService) createDeliveryTarget(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateDeliveryTargetRequest
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
}
// Call the usecase layer code
deliveryTargetDetail, err := dt.deliveryTargetUsecase.CreateDeliveryTarget(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("create delivery-target failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(deliveryTargetDetail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *DeliveryTargetWebService) deliveryTargetCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
deliveryTarget, err := dt.deliveryTargetUsecase.GetDeliveryTarget(req.Request.Context(), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyDeliveryTarget, deliveryTarget))
chain.ProcessFilter(req, res)
}
func (dt *DeliveryTargetWebService) detailDeliveryTarget(req *restful.Request, res *restful.Response) {
deliveryTarget := req.Request.Context().Value(&apis.CtxKeyDeliveryTarget).(*model.DeliveryTarget)
detail, err := dt.deliveryTargetUsecase.DetailDeliveryTarget(req.Request.Context(), deliveryTarget)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(detail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *DeliveryTargetWebService) updateDeliveryTarget(req *restful.Request, res *restful.Response) {
deliveryTarget := req.Request.Context().Value(&apis.CtxKeyDeliveryTarget).(*model.DeliveryTarget)
// Verify the validity of parameters
var updateReq apis.UpdateDeliveryTargetRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
detail, err := dt.deliveryTargetUsecase.UpdateDeliveryTarget(req.Request.Context(), deliveryTarget, updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(detail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *DeliveryTargetWebService) deleteDeliveryTarget(req *restful.Request, res *restful.Response) {
deliveryTargetName := req.PathParameter("name")
// deliveryTarget in use, can't be deleted
applications, err := dt.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicatioOptions{TargetName: deliveryTargetName})
if err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
bcode.ReturnError(req, res, err)
return
}
}
if applications != nil {
bcode.ReturnError(req, res, bcode.ErrDeliveryTargetInUseCantDeleted)
return
}
if err := dt.deliveryTargetUsecase.DeleteDeliveryTarget(req.Request.Context(), deliveryTargetName); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *DeliveryTargetWebService) listDeliveryTargets(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
deliveryTargets, err := dt.deliveryTargetUsecase.ListDeliveryTargets(req.Request.Context(), page, pageSize, req.QueryParameter("project"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(deliveryTargets); err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -0,0 +1,173 @@
/*
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 webservice
import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
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"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
type envWebService struct {
envUsecase usecase.EnvUsecase
appUsecase usecase.ApplicationUsecase
}
// NewEnvWebService new env webservice
func NewEnvWebService(envUsecase usecase.EnvUsecase, appUseCase usecase.ApplicationUsecase) WebService {
return &envWebService{envUsecase: envUsecase, appUsecase: appUseCase}
}
func (n *envWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/envs").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for env management")
tags := []string{"env"}
ws.Route(ws.GET("/").To(n.list).
Doc("list all envs").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.ListEnvResponse{}).
Writes(apis.ListEnvResponse{}))
ws.Route(ws.POST("/").To(n.create).
Doc("create an env").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateEnvRequest{}).
Returns(200, "", apis.Env{}).
Writes(apis.Env{}))
ws.Route(ws.PUT("/{name}").To(n.update).
Doc("update an env").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateEnvRequest{}).
Returns(200, "", apis.Env{}).
Writes(apis.Env{}))
ws.Route(ws.DELETE("/{name}").To(n.delete).
Doc("delete one env").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Returns(200, "", apis.EmptyResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
return ws
}
func (n *envWebService) list(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
project := req.QueryParameter("project")
envs, err := n.envUsecase.ListEnvs(req.Request.Context(), page, pageSize, apis.ListEnvOptions{Project: project})
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.ListEnvResponse{Envs: envs}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
// it will prevent the deletion if there's still application in it.
func (n *envWebService) delete(req *restful.Request, res *restful.Response) {
envname := req.PathParameter("name")
ctx := req.Request.Context()
lists, err := n.appUsecase.ListApplications(ctx, apis.ListApplicationOptions{Env: envname})
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if len(lists) > 0 {
log.Logger.Infof("detected %d applications in this env, the first is %s", len(lists), lists[0].Name)
bcode.ReturnError(req, res, bcode.ErrDeleteEnvButAppExist)
return
}
err = n.envUsecase.DeleteEnv(ctx, envname)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err = res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *envWebService) create(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateEnvRequest
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
}
// Call the usecase layer code
env, err := n.envUsecase.CreateEnv(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("create application failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(env); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *envWebService) update(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var updateReq apis.UpdateEnvRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
env, err := n.envUsecase.UpdateEnv(req.Request.Context(), req.PathParameter("name"), updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(env); err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -0,0 +1,215 @@
/*
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 webservice
import (
"context"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"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/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
// NewTargetWebService new Target webservice
func NewTargetWebService(targetUsecase usecase.TargetUsecase, applicationUsecase usecase.ApplicationUsecase) WebService {
return &TargetWebService{
TargetUsecase: targetUsecase,
applicationUsecase: applicationUsecase,
}
}
// TargetWebService target web service
type TargetWebService struct {
TargetUsecase usecase.TargetUsecase
applicationUsecase usecase.ApplicationUsecase
}
// GetWebService get web service
func (dt *TargetWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/targets").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for Target manage")
tags := []string{"Target"}
ws.Route(ws.GET("/").To(dt.listTargets).
Doc("list Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("page", "Page for paging").DataType("integer")).
Param(ws.QueryParameter("pageSize", "PageSize for paging").DataType("integer")).
Returns(200, "", apis.ListTargetResponse{}).
Writes(apis.ListTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.POST("/").To(dt.createTarget).
Doc("create Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateTargetRequest{}).
Returns(200, "create success", apis.DetailTargetResponse{}).
Returns(400, "create failure", bcode.Bcode{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}").To(dt.detailTarget).
Doc("detail Target").
Param(ws.PathParameter("name", "identifier of the Target.").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Returns(200, "create success", apis.DetailTargetResponse{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.PUT("/{name}").To(dt.updateTarget).
Doc("update application Target config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Param(ws.PathParameter("name", "identifier of the Target").DataType("string")).
Reads(apis.UpdateTargetRequest{}).
Returns(200, "", apis.DetailTargetResponse{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.DELETE("/{name}").To(dt.deleteTarget).
Doc("deletet Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Param(ws.PathParameter("name", "identifier of the Target").DataType("string")).
Returns(200, "", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}).Do(returns200, returns500))
return ws
}
func (dt *TargetWebService) createTarget(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateTargetRequest
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
}
// Call the usecase layer code
TargetDetail, err := dt.TargetUsecase.CreateTarget(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("create -target failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(TargetDetail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *TargetWebService) targetCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
Target, err := dt.TargetUsecase.GetTarget(req.Request.Context(), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyTarget, Target))
chain.ProcessFilter(req, res)
}
func (dt *TargetWebService) detailTarget(req *restful.Request, res *restful.Response) {
Target := req.Request.Context().Value(&apis.CtxKeyTarget).(*model.Target)
detail, err := dt.TargetUsecase.DetailTarget(req.Request.Context(), Target)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(detail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *TargetWebService) updateTarget(req *restful.Request, res *restful.Response) {
Target := req.Request.Context().Value(&apis.CtxKeyTarget).(*model.Target)
// Verify the validity of parameters
var updateReq apis.UpdateTargetRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
detail, err := dt.TargetUsecase.UpdateTarget(req.Request.Context(), Target, updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(detail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *TargetWebService) deleteTarget(req *restful.Request, res *restful.Response) {
TargetName := req.PathParameter("name")
// Target in use, can't be deleted
applications, err := dt.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicationOptions{TargetName: TargetName})
if err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
bcode.ReturnError(req, res, err)
return
}
}
if applications != nil {
bcode.ReturnError(req, res, bcode.ErrTargetInUseCantDeleted)
return
}
if err := dt.TargetUsecase.DeleteTarget(req.Request.Context(), TargetName); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (dt *TargetWebService) listTargets(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
Targets, err := dt.TargetUsecase.ListTargets(req.Request.Context(), page, pageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(Targets); err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -0,0 +1,71 @@
/*
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 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"
)
type webhookWebService struct {
webhookUsecase usecase.WebhookUsecase
applicationUsecase usecase.ApplicationUsecase
}
// NewWebhookWebService new application manage webservice
func NewWebhookWebService(webhookUsecase usecase.WebhookUsecase, applicationUsecase usecase.ApplicationUsecase) WebService {
return &webhookWebService{
webhookUsecase: webhookUsecase,
applicationUsecase: applicationUsecase,
}
}
func (c *webhookWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/webhook").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for webhook manage")
tags := []string{"webhook"}
ws.Route(ws.POST("/{token}").To(c.handleApplicationWebhook).
Doc("handle application webhook request").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.HandleApplicationWebhookRequest{}).
Returns(200, "", apis.ApplicationDeployResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationDeployResponse{}))
return ws
}
func (c *webhookWebService) handleApplicationWebhook(req *restful.Request, res *restful.Response) {
base, err := c.webhookUsecase.HandleApplicationWebhook(req.Request.Context(), req.PathParameter("token"), req)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -34,16 +34,16 @@ type WebService interface {
GetWebService() *restful.WebService
}
var registedWebService []WebService
var registeredWebService []WebService
// RegistWebService regist webservice
func RegistWebService(ws WebService) {
registedWebService = append(registedWebService, ws)
// RegisterWebService regist webservice
func RegisterWebService(ws WebService) {
registeredWebService = append(registeredWebService, ws)
}
// GetRegistedWebService return registedWebService
func GetRegistedWebService() []WebService {
return registedWebService
// GetRegisteredWebService return registeredWebService
func GetRegisteredWebService() []WebService {
return registeredWebService
}
func noop(req *restful.Request, resp *restful.Response) {}
@@ -60,24 +60,36 @@ func returns500(b *restful.RouteBuilder) {
// It can be implemented using the idea of dependency injection.
func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
clusterUsecase := usecase.NewClusterUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds)
envUsecase := usecase.NewEnvUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds, envUsecase)
projectUsecase := usecase.NewProjectUsecase(ds)
deliveryTargetUsecase := usecase.NewDeliveryTargetUsecase(ds, projectUsecase)
targetUsecase := usecase.NewTargetUsecase(ds)
oamApplicationUsecase := usecase.NewOAMApplicationUsecase()
velaQLUsecase := usecase.NewVelaQLUsecase()
definitionUsecase := usecase.NewDefinitionUsecase()
addonUsecase := usecase.NewAddonUsecase(addonCacheTime)
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, deliveryTargetUsecase, definitionUsecase, projectUsecase)
RegistWebService(NewClusterWebService(clusterUsecase))
RegistWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase, workflowUsecase))
RegistWebService(NewProjectWebService(projectUsecase))
RegistWebService(NewDefinitionWebservice(definitionUsecase))
RegistWebService(NewAddonWebService(addonUsecase))
RegistWebService(NewEnabledAddonWebService(addonUsecase))
RegistWebService(NewAddonRegistryWebService(addonUsecase))
RegistWebService(NewOAMApplication(oamApplicationUsecase))
RegistWebService(&policyDefinitionWebservice{})
RegistWebService(NewDeliveryTargetWebService(deliveryTargetUsecase, applicationUsecase))
RegistWebService(NewVelaQLWebService(velaQLUsecase))
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase, envUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
// init for default values
// Application
RegisterWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase, workflowUsecase))
RegisterWebService(NewProjectWebService(projectUsecase))
RegisterWebService(NewEnvWebService(envUsecase, applicationUsecase))
// Extension
RegisterWebService(NewDefinitionWebservice(definitionUsecase))
RegisterWebService(NewAddonWebService(addonUsecase))
RegisterWebService(NewEnabledAddonWebService(addonUsecase))
RegisterWebService(NewAddonRegistryWebService(addonUsecase))
// Resources
RegisterWebService(NewClusterWebService(clusterUsecase))
RegisterWebService(NewOAMApplication(oamApplicationUsecase))
RegisterWebService(&policyDefinitionWebservice{})
RegisterWebService(NewTargetWebService(targetUsecase, applicationUsecase))
RegisterWebService(NewVelaQLWebService(velaQLUsecase))
RegisterWebService(NewWebhookWebService(webhookUsecase, applicationUsecase))
}

View File

@@ -86,7 +86,7 @@ func GetMutableClusterSecret(ctx context.Context, c client.Client, clusterName s
for _, env := range status.Envs {
for _, placement := range env.Placements {
if placement.Cluster == clusterName {
errs.Append(fmt.Errorf("application %s/%s (env: %s) is currently using cluster %s", app.Namespace, app.Name, env.Env, clusterName))
errs = append(errs, fmt.Errorf("application %s/%s (env: %s) is currently using cluster %s", app.Namespace, app.Name, env.Env, clusterName))
}
}
}

Some files were not shown because too many files have changed in this diff Show More