mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
Feat: initialize the Apiserver framework (#2417)
* Feat: add kubeapi and mongodb datastore implementation * Style: change kubeapi import code style * Style: change mongodb package import code style * Style: add some comment * Style: change databasePrefix to tableNamePrefix * Chore: install mongodb in unit-test job * Chore: install mongodb in compatibility-test job * Feat: add apiserver e2e test case * Docs: change developer guide doc * Feat: use common.Scheme Co-authored-by: barnettZQG <yiyun.pro>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
name: apiServer-test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
workflow_dispatch: {}
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.16'
|
||||
GOLANGCI_VERSION: 'v1.38'
|
||||
KIND_VERSION: 'v0.7.0'
|
||||
|
||||
jobs:
|
||||
|
||||
detect-noop:
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
noop: ${{ steps.noop.outputs.should_skip }}
|
||||
steps:
|
||||
- name: Detect No-op Changes
|
||||
id: noop
|
||||
uses: fkirc/skip-duplicate-actions@v3.3.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
paths_ignore: '["**.md", "**.mdx", "**.png", "**.jpg"]'
|
||||
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
|
||||
concurrent_skipping: false
|
||||
|
||||
apiserver-unit-tests:
|
||||
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: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.6.0
|
||||
with:
|
||||
mongodb-version: 4.4
|
||||
|
||||
- name: Run apiserver unit test
|
||||
run: make unit-test-apiserver
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.txt
|
||||
flags: apiserver-unittests
|
||||
name: codecov-umbrella
|
||||
|
||||
- name: Run apiserver e2e test
|
||||
run: make e2e-apiserver-test
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
version: 3.1.0
|
||||
kubebuilderOnly: false
|
||||
kubernetesVersion: v1.21.2
|
||||
|
||||
|
||||
- name: Run Make test
|
||||
run: make test
|
||||
|
||||
@@ -81,5 +81,5 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.txt
|
||||
flags: unittests
|
||||
flags: core-unittests
|
||||
name: codecov-umbrella
|
||||
|
||||
@@ -43,11 +43,14 @@ VELA_RUNTIME_ROLLOUT_IMAGE ?= vela-runtime-rollout:latest
|
||||
all: build
|
||||
|
||||
# Run tests
|
||||
test: vet lint staticcheck
|
||||
go test -coverprofile=coverage.txt ./pkg/... ./cmd/...
|
||||
go test ./references/appfile/... ./references/cli/... ./references/common/... ./references/plugins/...
|
||||
test: vet lint staticcheck unit-test-core
|
||||
@$(OK) unit-tests pass
|
||||
|
||||
unit-test-core:
|
||||
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... ./references/... | grep -v apiserver)
|
||||
unit-test-apiserver:
|
||||
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep apiserver)
|
||||
|
||||
# Build vela cli binary
|
||||
build: fmt vet lint staticcheck vela-cli kubectl-vela
|
||||
@$(OK) build succeed
|
||||
@@ -163,6 +166,10 @@ e2e-api-test:
|
||||
ginkgo -v -skipPackage capability,setup,application -r e2e
|
||||
ginkgo -v -r e2e/application
|
||||
|
||||
e2e-apiserver-test:
|
||||
ginkgo -v ./test/e2e-apiserver-test
|
||||
@$(OK) tests pass
|
||||
|
||||
e2e-test:
|
||||
# Run e2e test
|
||||
ginkgo -v --skip="rollout related e2e-test." ./test/e2e-test
|
||||
@@ -178,7 +185,7 @@ e2e-multicluster-test:
|
||||
|
||||
compatibility-test: vet lint staticcheck generate-compatibility-testdata
|
||||
# Run compatibility test with old crd
|
||||
COMPATIBILITY_TEST=TRUE go test -race ./pkg/...
|
||||
COMPATIBILITY_TEST=TRUE go test -race $(shell go list ./pkg/... | grep -v apiserver)
|
||||
@$(OK) compatibility-test pass
|
||||
|
||||
generate-compatibility-testdata:
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := &server{}
|
||||
s := &Server{}
|
||||
flag.StringVar(&s.restCfg.BindAddr, "bind-addr", "0.0.0.0:8000", "The bind address used to serve the http APIs.")
|
||||
flag.StringVar(&s.restCfg.MetricPath, "metrics-path", "/metrics", "The path to expose the metrics.")
|
||||
flag.StringVar(&s.restCfg.Datastore.Type, "datastore-type", "kubeapi", "Metadata storage driver type, support kubeapi and mongodb")
|
||||
@@ -57,11 +57,12 @@ func main() {
|
||||
log.Logger.Infof("See you next time!")
|
||||
}
|
||||
|
||||
type server struct {
|
||||
// Server apiserver
|
||||
type Server struct {
|
||||
restCfg rest.Config
|
||||
}
|
||||
|
||||
func (s *server) run() error {
|
||||
func (s *Server) run() error {
|
||||
log.Logger.Infof("KubeVela information: version: %v, gitRevision: %v", version.VelaVersion, version.GitRevision)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -115,6 +115,12 @@ You can try use your local built binaries follow [the documentation](https://kub
|
||||
make test
|
||||
```
|
||||
|
||||
To execute the unit test of the API module, the mongodb service needs to exist locally.
|
||||
|
||||
```shell script
|
||||
make unit-test-apiserver
|
||||
```
|
||||
|
||||
### E2E test
|
||||
|
||||
**Before e2e test start, make sure you have vela-core running.**
|
||||
@@ -125,10 +131,16 @@ make core-run
|
||||
|
||||
Start to test.
|
||||
|
||||
```
|
||||
```shell script
|
||||
make e2e-test
|
||||
```
|
||||
|
||||
To execute the e2e test of the API module, the mongodb service needs to exist locally.
|
||||
|
||||
```shell script
|
||||
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ require (
|
||||
github.com/emicklei/go-restful/v3 v3.0.0-rc2
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/gertd/go-pluralize v0.1.7
|
||||
github.com/getkin/kin-openapi v0.34.0
|
||||
github.com/go-logr/logr v0.4.0
|
||||
@@ -51,6 +52,8 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.18.1
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
gotest.tools v2.2.0+incompatible
|
||||
helm.sh/helm/v3 v3.6.1
|
||||
|
||||
@@ -462,8 +462,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
|
||||
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
@@ -1592,6 +1593,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||
@@ -1842,8 +1844,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1978,8 +1981,10 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
@@ -2126,8 +2131,9 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6 h1:SIasE1FVIQOWz2GEAHFOmoW7xchJcqlucjSULTL0Ag4=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 clients
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var kubeClient client.Client
|
||||
|
||||
// SetKubeClient for test
|
||||
func SetKubeClient(c client.Client) {
|
||||
kubeClient = c
|
||||
}
|
||||
|
||||
// GetKubeClient create and return kube runtime client
|
||||
func GetKubeClient() (client.Client, error) {
|
||||
if kubeClient != nil {
|
||||
return kubeClient, nil
|
||||
}
|
||||
conf, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k8sClient, err := client.New(conf, client.Options{Scheme: common.Scheme})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k8sClient, nil
|
||||
}
|
||||
@@ -18,8 +18,41 @@ package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPrimaryEmpty Error that primary key is empty.
|
||||
ErrPrimaryEmpty = NewDBError(fmt.Errorf("entity primary is empty"))
|
||||
|
||||
// ErrTableNameEmpty Error that table name is empty.
|
||||
ErrTableNameEmpty = NewDBError(fmt.Errorf("entity table name is empty"))
|
||||
|
||||
// ErrNilEntity Error that entity is nil
|
||||
ErrNilEntity = NewDBError(fmt.Errorf("entity is nil"))
|
||||
|
||||
// ErrRecordExist Error that entity primary key is exist
|
||||
ErrRecordExist = NewDBError(fmt.Errorf("data record is exist"))
|
||||
|
||||
// ErrRecordNotExist Error that entity primary key is not exist
|
||||
ErrRecordNotExist = NewDBError(fmt.Errorf("data record is not exist"))
|
||||
)
|
||||
|
||||
// DBError datastore error
|
||||
type DBError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *DBError) Error() string {
|
||||
return d.err.Error()
|
||||
}
|
||||
|
||||
// NewDBError new datastore error
|
||||
func NewDBError(err error) error {
|
||||
return &DBError{err: err}
|
||||
}
|
||||
|
||||
// Config datastore config
|
||||
type Config struct {
|
||||
Type string
|
||||
@@ -27,31 +60,48 @@ type Config struct {
|
||||
Database string
|
||||
}
|
||||
|
||||
// Entity database data model
|
||||
type Entity interface {
|
||||
PrimaryKey() string
|
||||
TableName() string
|
||||
}
|
||||
|
||||
// NewEntity Create a new object based on the input type
|
||||
func NewEntity(in Entity) (Entity, error) {
|
||||
if in == nil {
|
||||
return nil, ErrNilEntity
|
||||
}
|
||||
t := reflect.TypeOf(in)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
new := reflect.New(t)
|
||||
return new.Interface().(Entity), nil
|
||||
}
|
||||
|
||||
// ListOptions list api options
|
||||
type ListOptions struct {
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// DataStore datastore interface
|
||||
type DataStore interface {
|
||||
Add(ctx context.Context, kind string, entity interface{}) error
|
||||
// add entity to database, Name() and TableName() can't return zero value.
|
||||
Add(ctx context.Context, entity Entity) error
|
||||
|
||||
Put(ctx context.Context, kind, name string, entity interface{}) error
|
||||
// Update entity to database, Name() and TableName() can't return zero value.
|
||||
Put(ctx context.Context, entity Entity) error
|
||||
|
||||
Delete(ctx context.Context, kind, name string) error
|
||||
// Delete entity from database, Name() and TableName() can't return zero value.
|
||||
Delete(ctx context.Context, entity Entity) error
|
||||
|
||||
Get(ctx context.Context, kind, name string, decodeTo interface{}) error
|
||||
// Get entity from database, Name() and TableName() can't return zero value.
|
||||
Get(ctx context.Context, entity Entity) error
|
||||
|
||||
// Find executes a find command and returns an iterator over the matching items.
|
||||
Find(ctx context.Context, kind string) (Iterator, error)
|
||||
// TableName() can't return zero value.
|
||||
List(ctx context.Context, query Entity, options *ListOptions) ([]Entity, error)
|
||||
|
||||
FindOne(ctx context.Context, kind, name string) (Iterator, error)
|
||||
|
||||
IsExist(ctx context.Context, kind, name string) (bool, error)
|
||||
}
|
||||
|
||||
// Iterator dataset query
|
||||
type Iterator interface {
|
||||
// Next gets the next item for this cursor.
|
||||
Next(ctx context.Context) bool
|
||||
|
||||
// Decode will unmarshal the current item into given entity.
|
||||
Decode(entity interface{}) error
|
||||
|
||||
Close(ctx context.Context) error
|
||||
// IsExist Name() and TableName() can't return zero value.
|
||||
IsExist(ctx context.Context, entity Entity) (bool, error)
|
||||
}
|
||||
|
||||
@@ -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 datastore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestDatastore(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Datastore Suite")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 datastore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
var _ = Describe("Test new entity function", func() {
|
||||
|
||||
It("Test new application entity", func() {
|
||||
var app model.Application
|
||||
new, err := NewEntity(&app)
|
||||
Expect(err).To(BeNil())
|
||||
json.Unmarshal([]byte(`{"name":"demo"}`), new)
|
||||
Expect(err).To(BeNil())
|
||||
diff := cmp.Diff(new.PrimaryKey(), "demo")
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test new multiple application entity", func() {
|
||||
var app model.Application
|
||||
var list []Entity
|
||||
var n = 3
|
||||
for n > 0 {
|
||||
new, err := NewEntity(&app)
|
||||
Expect(err).To(BeNil())
|
||||
json.Unmarshal([]byte(fmt.Sprintf(`{"name":"demo %d"}`, n)), new)
|
||||
Expect(err).To(BeNil())
|
||||
diff := cmp.Diff(new.PrimaryKey(), fmt.Sprintf("demo %d", n))
|
||||
Expect(diff).Should(BeEmpty())
|
||||
list = append(list, new)
|
||||
n--
|
||||
}
|
||||
diff := cmp.Diff(list[0].PrimaryKey(), "demo 3")
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
})
|
||||
@@ -18,50 +18,224 @@ package kubeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
)
|
||||
|
||||
type kubeapi struct {
|
||||
// kubeclient client.Client
|
||||
kubeclient client.Client
|
||||
namespace string
|
||||
}
|
||||
|
||||
// New new kubeapi datastore instance
|
||||
// Data is stored using ConfigMap.
|
||||
func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error) {
|
||||
return &kubeapi{}, nil
|
||||
kubeClient, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg.Database == "" {
|
||||
cfg.Database = "kubevela_store"
|
||||
}
|
||||
var namespace corev1.Namespace
|
||||
if err := kubeClient.Get(ctx, types.NamespacedName{Name: cfg.Database}, &namespace); apierrors.IsNotFound(err) {
|
||||
if err := kubeClient.Create(ctx, &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cfg.Database,
|
||||
Annotations: map[string]string{"description": "For kubevela apiserver metadata storage."},
|
||||
}}); err != nil {
|
||||
return nil, fmt.Errorf("create namesapce failure %w", err)
|
||||
}
|
||||
}
|
||||
return &kubeapi{
|
||||
kubeclient: kubeClient,
|
||||
namespace: cfg.Database,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateName(entity datastore.Entity) string {
|
||||
name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
|
||||
return strings.ReplaceAll(name, "_", "-")
|
||||
}
|
||||
|
||||
func (m *kubeapi) generateConfigMap(entity datastore.Entity) *corev1.ConfigMap {
|
||||
data, _ := json.Marshal(entity)
|
||||
var configMap = corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: generateName(entity),
|
||||
Namespace: m.namespace,
|
||||
Labels: map[string]string{
|
||||
"table": entity.TableName(),
|
||||
"primaryKey": entity.PrimaryKey(),
|
||||
},
|
||||
},
|
||||
BinaryData: map[string][]byte{
|
||||
"data": data,
|
||||
},
|
||||
}
|
||||
return &configMap
|
||||
}
|
||||
|
||||
// Add add data model
|
||||
func (m *kubeapi) Add(ctx context.Context, kind string, entity interface{}) error {
|
||||
func (m *kubeapi) Add(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
configMap := m.generateConfigMap(entity)
|
||||
if err := m.kubeclient.Create(ctx, configMap); err != nil {
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
return datastore.ErrRecordExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get get data model
|
||||
func (m *kubeapi) Get(ctx context.Context, kind, name string, decodeTo interface{}) error {
|
||||
func (m *kubeapi) Get(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
var configMap corev1.ConfigMap
|
||||
if err := m.kubeclient.Get(ctx, types.NamespacedName{Namespace: m.namespace, Name: generateName(entity)}, &configMap); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return datastore.ErrRecordNotExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
if err := json.Unmarshal(configMap.BinaryData["data"], entity); err != nil {
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put update data model
|
||||
func (m *kubeapi) Put(ctx context.Context, kind, name string, entity interface{}) error {
|
||||
func (m *kubeapi) Put(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
var configMap corev1.ConfigMap
|
||||
if err := m.kubeclient.Get(ctx, types.NamespacedName{Namespace: m.namespace, Name: generateName(entity)}, &configMap); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return datastore.ErrRecordNotExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
data, err := json.Marshal(entity)
|
||||
if err != nil {
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
configMap.BinaryData["data"] = data
|
||||
if err := m.kubeclient.Update(ctx, &configMap); err != nil {
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find find data model
|
||||
func (m *kubeapi) Find(ctx context.Context, kind string) (datastore.Iterator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FindOne find one data model
|
||||
func (m *kubeapi) FindOne(ctx context.Context, kind, name string) (datastore.Iterator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// IsExist determine whether data exists.
|
||||
func (m *kubeapi) IsExist(ctx context.Context, kind, name string) (bool, error) {
|
||||
func (m *kubeapi) IsExist(ctx context.Context, entity datastore.Entity) (bool, error) {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return false, datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return false, datastore.ErrTableNameEmpty
|
||||
}
|
||||
var configMap corev1.ConfigMap
|
||||
if err := m.kubeclient.Get(ctx, types.NamespacedName{Namespace: m.namespace, Name: generateName(entity)}, &configMap); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, datastore.NewDBError(err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Delete delete data
|
||||
func (m *kubeapi) Delete(ctx context.Context, kind, name string) error {
|
||||
func (m *kubeapi) Delete(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
if err := m.kubeclient.Delete(ctx, m.generateConfigMap(entity)); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return datastore.ErrRecordNotExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName() can't return zero value.
|
||||
func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datastore.ListOptions) ([]datastore.Entity, error) {
|
||||
if entity.TableName() == "" {
|
||||
return nil, datastore.ErrTableNameEmpty
|
||||
}
|
||||
selector, err := labels.Parse(fmt.Sprintf("table=%s", entity.TableName()))
|
||||
if err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
options := &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
}
|
||||
var skip, limit int64
|
||||
if op != nil && op.PageSize > 0 && op.Page > 0 {
|
||||
skip = int64(op.PageSize * (op.Page - 1))
|
||||
limit = int64(op.PageSize * op.Page)
|
||||
if skip < 0 {
|
||||
skip = 0
|
||||
}
|
||||
if limit < 0 {
|
||||
limit = skip
|
||||
}
|
||||
options.Limit = limit
|
||||
}
|
||||
var configMaps corev1.ConfigMapList
|
||||
if err := m.kubeclient.List(ctx, &configMaps, options); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
items := configMaps.Items
|
||||
if op != nil && op.PageSize > 0 && op.Page > 0 {
|
||||
if len(configMaps.Items) > int(limit) {
|
||||
items = configMaps.Items[skip:limit]
|
||||
} else {
|
||||
items = configMaps.Items[skip:]
|
||||
}
|
||||
}
|
||||
var list []datastore.Entity
|
||||
for _, item := range items {
|
||||
ent, err := datastore.NewEntity(entity)
|
||||
if err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
if err := json.Unmarshal(item.BinaryData["data"], ent); err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
list = append(list, ent)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
@@ -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 kubeapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestKubeapi(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Kubeapi Suite")
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
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 kubeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var testScheme = runtime.NewScheme()
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = scheme.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
var _ = Describe("Test kubeapi datastore driver", func() {
|
||||
|
||||
clients.SetKubeClient(k8sClient)
|
||||
kubeStore, err := New(context.TODO(), datastore.Config{Database: "test"})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(kubeStore).ToNot(BeNil())
|
||||
|
||||
It("Test add funtion", func() {
|
||||
err := kubeStore.Add(context.TODO(), &model.Application{Name: "kubevela-app", Description: "default"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Test get funtion", func() {
|
||||
app := &model.Application{Name: "kubevela-app"}
|
||||
err := kubeStore.Get(context.TODO(), app)
|
||||
Expect(err).Should(BeNil())
|
||||
diff := cmp.Diff(app.Description, "default")
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test put funtion", func() {
|
||||
err := kubeStore.Put(context.TODO(), &model.Application{Name: "kubevela-app", Description: "this is demo"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Test list funtion", func() {
|
||||
err := kubeStore.Add(context.TODO(), &model.Application{Name: "kubevela-app-2", Description: "this is demo 2"})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = kubeStore.Add(context.TODO(), &model.Application{Name: "kubevela-app-3", Description: "this is demo 3"})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
var app model.Application
|
||||
list, err := kubeStore.List(context.TODO(), &app, &datastore.ListOptions{Page: -1})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fmt.Printf("%+v", list[0])
|
||||
diff := cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{Page: 2, PageSize: 2})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 1)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{Page: 1, PageSize: 2})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 2)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = kubeStore.List(context.TODO(), &app, nil)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test isExist funtion", func() {
|
||||
var app model.Application
|
||||
app.Name = "kubevela-app-3"
|
||||
exist, err := kubeStore.IsExist(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff := cmp.Diff(exist, true)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
app.Name = "kubevela-app-4"
|
||||
notexist, err := kubeStore.IsExist(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(notexist, false)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test delete funtion", func() {
|
||||
var app model.Application
|
||||
app.Name = "kubevela-app"
|
||||
err := kubeStore.Delete(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-2"
|
||||
err = kubeStore.Delete(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-3"
|
||||
err = kubeStore.Delete(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-3"
|
||||
err = kubeStore.Delete(context.TODO(), &app)
|
||||
equal := cmp.Equal(err, datastore.ErrRecordNotExist, cmpopts.EquateErrors())
|
||||
Expect(equal).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
)
|
||||
|
||||
type mongodb struct {
|
||||
@@ -36,7 +37,7 @@ type mongodb struct {
|
||||
|
||||
// New new mongodb datastore instance
|
||||
func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error) {
|
||||
if strings.HasPrefix(cfg.URL, "mongodb://") {
|
||||
if !strings.HasPrefix(cfg.URL, "mongodb://") {
|
||||
cfg.URL = fmt.Sprintf("mongodb://%s", cfg.URL)
|
||||
}
|
||||
clientOpts := options.Client().ApplyURI(cfg.URL)
|
||||
@@ -53,70 +54,93 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
|
||||
}
|
||||
|
||||
// Add add data model
|
||||
func (m *mongodb) Add(ctx context.Context, kind string, entity interface{}) error {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
func (m *mongodb) Add(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
if err := m.Get(ctx, entity); err == nil {
|
||||
return datastore.ErrRecordExist
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
_, err := collection.InsertOne(ctx, entity)
|
||||
if err != nil {
|
||||
return err
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get get data model
|
||||
func (m *mongodb) Get(ctx context.Context, kind, name string, decodeTo interface{}) error {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
return collection.FindOne(ctx, makeNameFilter(name)).Decode(decodeTo)
|
||||
}
|
||||
|
||||
// Put update data model
|
||||
func (m *mongodb) Put(ctx context.Context, kind, name string, entity interface{}) error {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
_, err := collection.UpdateOne(ctx, makeNameFilter(name), makeEntityUpdate(entity))
|
||||
if err != nil {
|
||||
return err
|
||||
func (m *mongodb) Get(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
if err := collection.FindOne(ctx, makeNameFilter(entity.PrimaryKey())).Decode(entity); err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
return datastore.ErrRecordNotExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find find data model
|
||||
func (m *mongodb) Find(ctx context.Context, kind string) (datastore.Iterator, error) {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
// bson.D{{}} specifies 'all documents'
|
||||
filter := bson.D{}
|
||||
cur, err := collection.Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Put update data model
|
||||
func (m *mongodb) Put(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
return &Iterator{cur: cur}, nil
|
||||
}
|
||||
|
||||
// FindOne find one data model
|
||||
func (m *mongodb) FindOne(ctx context.Context, kind, name string) (datastore.Iterator, error) {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
filter := bson.M{"name": name}
|
||||
cur, err := collection.Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
return &Iterator{cur: cur}, nil
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
_, err := collection.UpdateOne(ctx, makeNameFilter(entity.PrimaryKey()), makeEntityUpdate(entity))
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
return datastore.ErrRecordNotExist
|
||||
}
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExist determine whether data exists.
|
||||
func (m *mongodb) IsExist(ctx context.Context, kind, name string) (bool, error) {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
err := collection.FindOne(ctx, makeNameFilter(name)).Err()
|
||||
func (m *mongodb) IsExist(ctx context.Context, entity datastore.Entity) (bool, error) {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return false, datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return false, datastore.ErrTableNameEmpty
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
err := collection.FindOne(ctx, makeNameFilter(entity.PrimaryKey())).Err()
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
return false, datastore.NewDBError(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Delete delete data
|
||||
func (m *mongodb) Delete(ctx context.Context, kind, name string) error {
|
||||
collection := m.client.Database(m.database).Collection(kind)
|
||||
func (m *mongodb) Delete(ctx context.Context, entity datastore.Entity) error {
|
||||
if entity.PrimaryKey() == "" {
|
||||
return datastore.ErrPrimaryEmpty
|
||||
}
|
||||
if entity.TableName() == "" {
|
||||
return datastore.ErrTableNameEmpty
|
||||
}
|
||||
// check entity is exist
|
||||
if err := m.Get(ctx, entity); err != nil {
|
||||
return err
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
// delete at most one document in which the "name" field is "Bob" or "bob"
|
||||
// specify the SetCollation option to provide a collation that will ignore case for string comparisons
|
||||
opts := options.Delete().SetCollation(&options.Collation{
|
||||
@@ -124,8 +148,51 @@ func (m *mongodb) Delete(ctx context.Context, kind, name string) error {
|
||||
Strength: 1,
|
||||
CaseLevel: false,
|
||||
})
|
||||
_, err := collection.DeleteOne(ctx, makeNameFilter(name), opts)
|
||||
return err
|
||||
_, err := collection.DeleteOne(ctx, makeNameFilter(entity.PrimaryKey()), opts)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("delete document failure %w", err)
|
||||
return datastore.NewDBError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List list entity function
|
||||
func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datastore.ListOptions) ([]datastore.Entity, error) {
|
||||
if entity.TableName() == "" {
|
||||
return nil, datastore.ErrTableNameEmpty
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
// bson.D{{}} specifies 'all documents'
|
||||
filter := bson.D{}
|
||||
var findOptions options.FindOptions
|
||||
if op != nil && op.PageSize > 0 && op.Page > 0 {
|
||||
findOptions.SetSkip(int64(op.PageSize * (op.Page - 1)))
|
||||
findOptions.SetLimit(int64(op.PageSize))
|
||||
}
|
||||
cur, err := collection.Find(ctx, filter, &findOptions)
|
||||
if err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := cur.Close(ctx); err != nil {
|
||||
log.Logger.Warnf("close mongodb cursor failure %s", err.Error())
|
||||
}
|
||||
}()
|
||||
var list []datastore.Entity
|
||||
for cur.Next(ctx) {
|
||||
item, err := datastore.NewEntity(entity)
|
||||
if err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
if err := cur.Decode(item); err != nil {
|
||||
return nil, datastore.NewDBError(fmt.Errorf("decode entity failure %w", err))
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
if err := cur.Err(); err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func makeNameFilter(name string) bson.D {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
)
|
||||
|
||||
func TestMongodb(t *testing.T) {
|
||||
_, err := New(context.TODO(), datastore.Config{
|
||||
URL: "mongodb://localhost:27017",
|
||||
Database: "kubevela",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Mongodb Suite")
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
var mongodbDriver datastore.DataStore
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
By("bootstrapping mongodb test environment")
|
||||
var err error
|
||||
mongodbDriver, err = New(context.TODO(), datastore.Config{
|
||||
URL: "mongodb://localhost:27017",
|
||||
Database: "kubevela",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(mongodbDriver).ToNot(BeNil())
|
||||
|
||||
mongodbDriver, err = New(context.TODO(), datastore.Config{
|
||||
URL: "localhost:27017",
|
||||
Database: "kubevela",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(mongodbDriver).ToNot(BeNil())
|
||||
By("create mongodb driver success")
|
||||
close(done)
|
||||
}, 120)
|
||||
|
||||
var _ = Describe("Test mongodb datastore driver", func() {
|
||||
|
||||
It("Test add funtion", func() {
|
||||
err := mongodbDriver.Add(context.TODO(), &model.Application{Name: "kubevela-app", Description: "default"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Test get funtion", func() {
|
||||
app := &model.Application{Name: "kubevela-app"}
|
||||
err := mongodbDriver.Get(context.TODO(), app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff := cmp.Diff(app.Description, "default")
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test put funtion", func() {
|
||||
err := mongodbDriver.Put(context.TODO(), &model.Application{Name: "kubevela-app", Description: "this is demo"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Test list funtion", func() {
|
||||
err := mongodbDriver.Add(context.TODO(), &model.Application{Name: "kubevela-app-2", Description: "this is demo 2"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = mongodbDriver.Add(context.TODO(), &model.Application{Name: "kubevela-app-3", Description: "this is demo 3"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
var app model.Application
|
||||
list, err := mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{Page: -1})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff := cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{Page: 2, PageSize: 1})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 1)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{Page: 1, PageSize: 2})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 2)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = mongodbDriver.List(context.TODO(), &app, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test isExist funtion", func() {
|
||||
var app model.Application
|
||||
app.Name = "kubevela-app-3"
|
||||
exist, err := mongodbDriver.IsExist(context.TODO(), &app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff := cmp.Diff(exist, true)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
app.Name = "kubevela-app-4"
|
||||
notexist, err := mongodbDriver.IsExist(context.TODO(), &app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
diff = cmp.Diff(notexist, false)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test delete funtion", func() {
|
||||
var app model.Application
|
||||
app.Name = "kubevela-app"
|
||||
err := mongodbDriver.Delete(context.TODO(), &app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-2"
|
||||
err = mongodbDriver.Delete(context.TODO(), &app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-3"
|
||||
err = mongodbDriver.Delete(context.TODO(), &app)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
app.Name = "kubevela-app-3"
|
||||
err = mongodbDriver.Delete(context.TODO(), &app)
|
||||
equal := cmp.Equal(err, datastore.ErrRecordNotExist, cmpopts.EquateErrors())
|
||||
Expect(equal).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// Application database model
|
||||
type Application struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
ClusterList []string `json:"clusterList,omitempty"`
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
func (a *Application) TableName() string {
|
||||
return tableNamePrefix + "application"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (a *Application) PrimaryKey() string {
|
||||
return a.Name
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
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
|
||||
|
||||
var tableNamePrefix = "vela_"
|
||||
@@ -18,6 +18,9 @@ package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
// AddonPhase defines the phase of an addon
|
||||
@@ -36,7 +39,7 @@ const (
|
||||
|
||||
// CreateAddonRequest defines the format for addon create request
|
||||
type CreateAddonRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Name string `json:"name" validate:"name"`
|
||||
|
||||
Version string `json:"version" validate:"required"`
|
||||
|
||||
@@ -97,7 +100,7 @@ type AddonStatusResponse struct {
|
||||
|
||||
// CreateClusterRequest request parameters to create a cluster
|
||||
type CreateClusterRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Name string `json:"name" validate:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Icon string `json:"icon"`
|
||||
KubeConfig string `json:"kubeConfig" validate:"required_without=kubeConfigSecret"`
|
||||
@@ -180,13 +183,15 @@ type GatewayRule struct {
|
||||
|
||||
// CreateApplicationRequest create application request body
|
||||
type CreateApplicationRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Namespace string `json:"namespace" validate:"required"`
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Namespace string `json:"namespace" validate:"checkname"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
ClusterList []string `json:"clusterList,omitempty"`
|
||||
YamlConfig string `json:"yamlConfig,omitempty"`
|
||||
// Deploy Setting this to true means that the application is deployed directly after creation.
|
||||
Deploy bool `json:"deploy,omitempty"`
|
||||
}
|
||||
|
||||
// DetailApplicationResponse application detail
|
||||
@@ -219,7 +224,7 @@ type ComponentBase struct {
|
||||
ComponentType string `json:"componentType"`
|
||||
BindClusters []string `json:"bindClusters"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
DependOn []string `json:"dependOn"`
|
||||
DependsOn []string `json:"dependsOn"`
|
||||
Creator string `json:"creator,omitempty"`
|
||||
DeployVersion string `json:"deployVersion"`
|
||||
}
|
||||
@@ -231,7 +236,7 @@ type ComponentListResponse struct {
|
||||
|
||||
// CreateComponentRequest create component request model
|
||||
type CreateComponentRequest struct {
|
||||
ApplicationName string `json:"appName" validate:"required"`
|
||||
ApplicationName string `json:"appName" validate:"name"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
@@ -275,7 +280,7 @@ type NamesapceBase struct {
|
||||
|
||||
// CreateNamespaceRequest create namespace request body
|
||||
type CreateNamespaceRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Name string `json:"name" validate:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -292,17 +297,79 @@ type ListComponentDefinitionResponse struct {
|
||||
|
||||
// ComponentDefinitionBase component definition base model
|
||||
type ComponentDefinitionBase struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
RequiredParams []Param `json:"requiredParams"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Parameter []types.Parameter `json:"requiredParams"`
|
||||
}
|
||||
|
||||
// Param For rendering forms
|
||||
type Param struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
DefaultValue interface{} `json:"defaultValue"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
// CreatePolicyRequest create app policy
|
||||
type CreatePolicyRequest struct {
|
||||
// Name is the unique name of the policy.
|
||||
Name string `json:"name" validate:"name"`
|
||||
|
||||
Type string `json:"type" validate:"required"`
|
||||
|
||||
// Properties json data
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
// DetailPolicyResponse app policy detail model
|
||||
type DetailPolicyResponse struct {
|
||||
// Name is the unique name of the policy.
|
||||
Name string `json:"name"`
|
||||
|
||||
Type string `json:"type"`
|
||||
|
||||
// Properties json data
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
// ListPolicyDefinitionResponse list available
|
||||
type ListPolicyDefinitionResponse struct {
|
||||
PolicyDefinitions []PolicyDefinition `json:"policyDefinitions"`
|
||||
}
|
||||
|
||||
// PolicyDefinition application policy definition
|
||||
type PolicyDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters []types.Parameter `json:"parameters"`
|
||||
}
|
||||
|
||||
// UpdateWorkflowRequest update or create application workflow
|
||||
type UpdateWorkflowRequest struct {
|
||||
Steps []WorkflowStep `json:"steps,omitempty"`
|
||||
Enable bool `json:"enable"`
|
||||
}
|
||||
|
||||
// WorkflowStep workflow step config
|
||||
type WorkflowStep struct {
|
||||
// Name is the unique name of the workflow step.
|
||||
Name string `json:"name"`
|
||||
|
||||
Type string `json:"type"`
|
||||
|
||||
Properties string `json:"properties,omitempty"`
|
||||
|
||||
Inputs common.StepInputs `json:"inputs,omitempty"`
|
||||
|
||||
Outputs common.StepOutputs `json:"outputs,omitempty"`
|
||||
}
|
||||
|
||||
// DetailWorkflowResponse detail workflow response
|
||||
type DetailWorkflowResponse struct {
|
||||
Steps []WorkflowStep `json:"steps,omitempty"`
|
||||
Enable bool `json:"enable"`
|
||||
LastRecord *WorkflowRecord `json:"workflowRecord"`
|
||||
}
|
||||
|
||||
// ListWorkflowRecordsResponse list workflow execution record
|
||||
type ListWorkflowRecordsResponse struct {
|
||||
Records []WorkflowRecord `json:"records"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// WorkflowRecord workflow record
|
||||
type WorkflowRecord struct {
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func New(cfg Config) (a APIServer, err error) {
|
||||
}
|
||||
|
||||
func (s *restServer) Run(ctx context.Context) error {
|
||||
webservice.Init(ctx)
|
||||
webservice.Init(ctx, s.dataStore)
|
||||
err := s.registerServices()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"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/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"
|
||||
)
|
||||
|
||||
// ApplicationUsecase application usecase
|
||||
type ApplicationUsecase interface {
|
||||
CreateApplication(context.Context, apisv1.CreateApplicationRequest) (*apisv1.ApplicationBase, error)
|
||||
}
|
||||
|
||||
type applicationUsecaseImpl struct {
|
||||
ds datastore.DataStore
|
||||
}
|
||||
|
||||
// NewApplicationUsecase new cluster usecase
|
||||
func NewApplicationUsecase(ds datastore.DataStore) ApplicationUsecase {
|
||||
return &applicationUsecaseImpl{ds: ds}
|
||||
}
|
||||
|
||||
// CreateApplication create application
|
||||
func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apisv1.CreateApplicationRequest) (*apisv1.ApplicationBase, error) {
|
||||
application := model.Application{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Icon: req.Icon,
|
||||
Labels: req.Labels,
|
||||
ClusterList: req.ClusterList,
|
||||
}
|
||||
// check clusters.
|
||||
|
||||
// check can deploy
|
||||
var canDeploy bool
|
||||
if req.YamlConfig != "" {
|
||||
var oamApp v1beta1.Application
|
||||
if err := json.Unmarshal([]byte(req.YamlConfig), &oamApp); err != nil {
|
||||
log.Logger.Errorf("application yaml config is invalid,%s", err.Error())
|
||||
return nil, bcode.ErrApplicationConfig
|
||||
}
|
||||
// TODO: check oam spec
|
||||
|
||||
// TODO: split the configuration and store it in the database.
|
||||
|
||||
canDeploy = true
|
||||
}
|
||||
|
||||
// add to db.
|
||||
if err := c.ds.Add(ctx, &application); err != nil {
|
||||
if errors.Is(err, datastore.ErrRecordExist) {
|
||||
return nil, bcode.ErrApplicationExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// render app base info.
|
||||
base := c.renderAppBase(&application)
|
||||
// deploy to cluster if need.
|
||||
if req.Deploy && canDeploy {
|
||||
if err := c.Deploy(ctx, req.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// Deploy deploy app to cluster
|
||||
// means to render oam application config and apply to cluster.
|
||||
// An event record is generated for each deploy.
|
||||
func (c *applicationUsecaseImpl) Deploy(ctx context.Context, appName string) error {
|
||||
// TODO:
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationUsecaseImpl) renderAppBase(app *model.Application) *apisv1.ApplicationBase {
|
||||
appBeas := &apisv1.ApplicationBase{
|
||||
Name: app.Name,
|
||||
Description: app.Description,
|
||||
Icon: app.Icon,
|
||||
Labels: app.Labels,
|
||||
}
|
||||
// TODO: get and render app status
|
||||
return appBeas
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// ErrApplicationConfig application config does not comply with OAM specification
|
||||
var ErrApplicationConfig = NewBcode(400, 10000, "application config does not comply with OAM specification")
|
||||
|
||||
// ErrComponentTypeNotSupport an unsupported component type was used.
|
||||
var ErrComponentTypeNotSupport = NewBcode(400, 10001, "An unsupported component type was used.")
|
||||
|
||||
// ErrApplicationExist application is exist
|
||||
var ErrApplicationExist = NewBcode(400, 10002, "application name is exist")
|
||||
@@ -37,31 +37,48 @@ func (b *Bcode) Error() string {
|
||||
return fmt.Sprintf("HTTPCode:%d BusinessCode:%d Message:%s", b.HTTPCode, b.BusinessCode, b.Message)
|
||||
}
|
||||
|
||||
var bcodeMap map[int32]*Bcode
|
||||
|
||||
// NewBcode new business code
|
||||
func NewBcode(httpCode, businessCode int32, message string) *Bcode {
|
||||
if bcodeMap == nil {
|
||||
bcodeMap = make(map[int32]*Bcode)
|
||||
}
|
||||
if _, exit := bcodeMap[businessCode]; exit {
|
||||
panic("bcode business code is exist")
|
||||
}
|
||||
bcode := &Bcode{HTTPCode: httpCode, BusinessCode: businessCode, Message: message}
|
||||
bcodeMap[businessCode] = bcode
|
||||
return bcode
|
||||
}
|
||||
|
||||
// ReturnError Unified handling of all types of errors, generating a standard return structure.
|
||||
func ReturnError(req *restful.Request, res *restful.Response, err error) {
|
||||
var bcode *Bcode
|
||||
if errors.As(err, &bcode) {
|
||||
if err := res.WriteEntity(err); err != nil {
|
||||
if err := res.WriteHeaderAndEntity(int(bcode.HTTPCode), err); err != nil {
|
||||
log.Logger.Error("write entity failure %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
var restfulerr *restful.ServiceError
|
||||
if errors.As(err, restfulerr) {
|
||||
if err := res.WriteEntity(Bcode{HTTPCode: int32(restfulerr.Code), BusinessCode: int32(restfulerr.Code), Message: restfulerr.Message}); err != nil {
|
||||
var restfulerr restful.ServiceError
|
||||
if errors.As(err, &restfulerr) {
|
||||
if err := res.WriteHeaderAndEntity(restfulerr.Code, Bcode{HTTPCode: int32(restfulerr.Code), BusinessCode: int32(restfulerr.Code), Message: restfulerr.Message}); err != nil {
|
||||
log.Logger.Error("write entity failure %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
var validErr *validator.ValidationErrors
|
||||
if errors.As(err, validErr) {
|
||||
if err := res.WriteEntity(Bcode{HTTPCode: 400, BusinessCode: 400, Message: err.Error()}); err != nil {
|
||||
|
||||
var validErr validator.ValidationErrors
|
||||
if errors.As(err, &validErr) {
|
||||
if err := res.WriteHeaderAndEntity(400, Bcode{HTTPCode: 400, BusinessCode: 400, Message: err.Error()}); err != nil {
|
||||
log.Logger.Error("write entity failure %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Logger.Errorf("Business exceptions, message %s, path:%s method:%s", err.Error(), req.Request.URL, req.Request.Method)
|
||||
if err := res.WriteEntity(Bcode{HTTPCode: 500, BusinessCode: 500, Message: err.Error()}); err != nil {
|
||||
|
||||
log.Logger.Errorf("Business exceptions, error message: %s, path:%s method:%s", err.Error(), req.Request.URL, req.Request.Method)
|
||||
if err := res.WriteHeaderAndEntity(500, Bcode{HTTPCode: 500, BusinessCode: 500, Message: err.Error()}); err != nil {
|
||||
log.Logger.Error("write entity failure %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,19 @@ import (
|
||||
restful "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 applicationWebService struct {
|
||||
applicationUsecase usecase.ApplicationUsecase
|
||||
}
|
||||
|
||||
// NewApplicationWebService new application manage webservice
|
||||
func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase) WebService {
|
||||
return &applicationWebService{
|
||||
applicationUsecase: applicationUsecase,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *applicationWebService) GetWebService() *restful.WebService {
|
||||
@@ -35,7 +45,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
|
||||
|
||||
tags := []string{"application"}
|
||||
|
||||
ws.Route(ws.GET("/").To(noop).
|
||||
ws.Route(ws.GET("/").To(c.listApplications).
|
||||
Doc("list all applications").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
|
||||
@@ -43,7 +53,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
|
||||
Param(ws.QueryParameter("cluster", "Cluster-based search").DataType("string")).
|
||||
Writes(apis.ListApplicationResponse{}))
|
||||
|
||||
ws.Route(ws.POST("/").To(noop).
|
||||
ws.Route(ws.POST("/").To(c.createApplication).
|
||||
Doc("create one application").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Reads(apis.CreateApplicationRequest{}).
|
||||
@@ -77,6 +87,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
|
||||
ws.Route(ws.GET("/{name}/components").To(noop).
|
||||
Doc("gets the component topology of the application").
|
||||
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
|
||||
Param(ws.PathParameter("cluster", "list components that deployed in define cluster").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Writes(apis.ComponentListResponse{}))
|
||||
|
||||
@@ -86,5 +97,55 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Reads(apis.CreateComponentRequest{}).
|
||||
Writes(apis.ComponentBase{}))
|
||||
|
||||
ws.Route(ws.POST("/{name}/policies").To(noop).
|
||||
Doc("create policy for application").
|
||||
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Reads(apis.CreatePolicyRequest{}).
|
||||
Writes(apis.DetailPolicyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{name}/policies/{policyName}").To(noop).
|
||||
Doc("detail policy for application").
|
||||
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
|
||||
Param(ws.PathParameter("policyName", "identifier of the application policy").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Writes(apis.DetailPolicyResponse{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{name}/policies/{policyName}").To(noop).
|
||||
Doc("detail policy for application").
|
||||
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
|
||||
Param(ws.PathParameter("policyName", "identifier of the application policy").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Writes(apis.DetailPolicyResponse{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
func (c *applicationWebService) createApplication(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var createReq apis.CreateApplicationRequest
|
||||
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
|
||||
appBase, err := c.applicationUsecase.CreateApplication(req.Request.Context(), createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write back response data
|
||||
if err := res.WriteEntity(appBase); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *applicationWebService) listApplications(req *restful.Request, res *restful.Response) {
|
||||
|
||||
}
|
||||
|
||||
@@ -25,11 +25,18 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
)
|
||||
|
||||
type clusterWebService struct {
|
||||
// ClusterWebService cluster manage webservice
|
||||
type ClusterWebService struct {
|
||||
clusterUsecase usecase.ClusterUsecase
|
||||
}
|
||||
|
||||
func (c *clusterWebService) GetWebService() *restful.WebService {
|
||||
// NewClusterWebService new cluster webservice
|
||||
func NewClusterWebService(clusterUsecase usecase.ClusterUsecase) *ClusterWebService {
|
||||
return &ClusterWebService{clusterUsecase: clusterUsecase}
|
||||
}
|
||||
|
||||
// GetWebService -
|
||||
func (c *ClusterWebService) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/clusters").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
@@ -71,7 +78,7 @@ func (c *clusterWebService) GetWebService() *restful.WebService {
|
||||
return ws
|
||||
}
|
||||
|
||||
func (c *clusterWebService) createKubeCluster(req *restful.Request, res *restful.Response) {
|
||||
func (c *ClusterWebService) createKubeCluster(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var createReq apis.CreateClusterRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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"
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
type policyDefinitionWebservice struct {
|
||||
}
|
||||
|
||||
func (c *policyDefinitionWebservice) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/policydefinitions").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for policydefinition manage")
|
||||
|
||||
tags := []string{"policydefinition"}
|
||||
|
||||
ws.Route(ws.GET("/").To(noop).
|
||||
Doc("list all policydefinition").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Writes(apis.ListPolicyDefinitionResponse{}))
|
||||
return ws
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
var validate = validator.New()
|
||||
|
||||
var nameRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
|
||||
|
||||
func init() {
|
||||
if err := validate.RegisterValidation("checkname", ValidateName); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateName custom check name field
|
||||
func ValidateName(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
if len(value) > 32 || len(value) < 2 {
|
||||
return false
|
||||
}
|
||||
return nameRegexp.MatchString(value)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("Test validate function", func() {
|
||||
It("Test check name validate ", func() {
|
||||
Expect(cmp.Diff(nameRegexp.MatchString("///Asd asda "), false)).Should(BeEmpty())
|
||||
var app0 = apisv1.CreateApplicationRequest{
|
||||
Name: "a",
|
||||
Namespace: "namesapce",
|
||||
}
|
||||
err := validate.Struct(&app0)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
var app1 = apisv1.CreateApplicationRequest{
|
||||
Name: "Asdasd",
|
||||
Namespace: "namesapce",
|
||||
}
|
||||
err = validate.Struct(&app1)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
var app2 = apisv1.CreateApplicationRequest{
|
||||
Name: "asdasd asdasd ++",
|
||||
Namespace: "namesapce",
|
||||
}
|
||||
err = validate.Struct(&app2)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
var app3 = apisv1.CreateApplicationRequest{
|
||||
Name: "asdasd",
|
||||
Namespace: "namesapce",
|
||||
}
|
||||
err = validate.Struct(&app3)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
})
|
||||
@@ -21,14 +21,14 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
|
||||
)
|
||||
|
||||
// versionPrefix API version prefix.
|
||||
var versionPrefix = "/api/v1"
|
||||
|
||||
var validate = validator.New()
|
||||
|
||||
// WebService webservice interface
|
||||
type WebService interface {
|
||||
GetWebService() *restful.WebService
|
||||
@@ -57,11 +57,16 @@ func returns500(b *restful.RouteBuilder) {
|
||||
}
|
||||
|
||||
// Init init all webservice, pass in the required parameter object.
|
||||
func Init(ctx context.Context) {
|
||||
RegistWebService(&clusterWebService{})
|
||||
RegistWebService(&applicationWebService{})
|
||||
// It can be implemented using the idea of dependency injection.
|
||||
func Init(ctx context.Context, ds datastore.DataStore) {
|
||||
clusterUsecase := usecase.NewClusterUsecase(ds)
|
||||
applicationUsecase := usecase.NewApplicationUsecase(ds)
|
||||
RegistWebService(NewClusterWebService(clusterUsecase))
|
||||
RegistWebService(NewApplicationWebService(applicationUsecase))
|
||||
RegistWebService(&namespaceWebService{})
|
||||
RegistWebService(&componentDefinitionWebservice{})
|
||||
RegistWebService(&addonWebService{})
|
||||
RegistWebService(&oamApplicationWebService{})
|
||||
RegistWebService(&policyDefinitionWebservice{})
|
||||
RegistWebService(&workflowWebService{})
|
||||
}
|
||||
|
||||
@@ -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 webservice_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestWebservice(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Webservice Suite")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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"
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
type workflowWebService struct {
|
||||
}
|
||||
|
||||
func (c *workflowWebService) GetWebService() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/workflows").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for cluster manage")
|
||||
|
||||
tags := []string{"cluster"}
|
||||
|
||||
ws.Route(ws.GET("/{name}").To(noop).
|
||||
Doc("detail application workflow").
|
||||
Param(ws.PathParameter("name", "identifier of the workflow, Currently, the application name is used.").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500))
|
||||
|
||||
ws.Route(ws.PUT("/{name}").To(noop).
|
||||
Doc("create or update application workflow config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")).
|
||||
Reads(apis.UpdateWorkflowRequest{}).
|
||||
Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500))
|
||||
|
||||
ws.Route(ws.GET("/{name}/records").To(noop).
|
||||
Doc("query application workflow execution record").
|
||||
Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.PathParameter("page", "Query the page number.").DataType("integer")).
|
||||
Param(ws.PathParameter("pageSize", "Query the page size number.").DataType("integer")).
|
||||
Writes(apis.ListWorkflowRecordsResponse{}).Do(returns200, returns500))
|
||||
|
||||
return ws
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("Test application rest api", func() {
|
||||
It("Test create app", func() {
|
||||
defer GinkgoRecover()
|
||||
var req = apisv1.CreateApplicationRequest{
|
||||
Name: "test-app-sadasd",
|
||||
Namespace: "test-app-namesapce",
|
||||
Description: "this is a test app",
|
||||
Icon: "",
|
||||
Labels: map[string]string{"test": "true"},
|
||||
ClusterList: []string{},
|
||||
}
|
||||
bodyByte, err := json.Marshal(req)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
res, err := http.Post("http://127.0.0.1:8000/api/v1/applications", "application/json", bytes.NewBuffer(bodyByte))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty())
|
||||
Expect(res.Body).ShouldNot(BeNil())
|
||||
defer res.Body.Close()
|
||||
var appBase apisv1.ApplicationBase
|
||||
err = json.NewDecoder(res.Body).Decode(&appBase)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cmp.Diff(appBase.Name, req.Name)).Should(BeEmpty())
|
||||
Expect(cmp.Diff(appBase.Description, req.Description)).Should(BeEmpty())
|
||||
Expect(cmp.Diff(appBase.Labels["test"], req.Labels["test"])).Should(BeEmpty())
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
arest "github.com/oam-dev/kubevela/pkg/apiserver/rest"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var testScheme = runtime.NewScheme()
|
||||
|
||||
func TestE2eApiserverTest(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "E2eApiserverTest Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = scheme.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
clients.SetKubeClient(k8sClient)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
server, err := arest.New(arest.Config{
|
||||
BindAddr: "127.0.0.1:8000",
|
||||
Datastore: datastore.Config{
|
||||
Type: "kubeapi",
|
||||
Database: "kubevela",
|
||||
},
|
||||
})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(server).ShouldNot(BeNil())
|
||||
go func() {
|
||||
err = server.Run(ctx)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
}()
|
||||
By("api server started")
|
||||
time.Sleep(time.Second * 2)
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
Reference in New Issue
Block a user