Feat: add namesapce create and list api (#2514)

Co-authored-by: barnettZQG <yiyun.pro>
This commit is contained in:
barnettZQG
2021-10-23 00:29:43 +08:00
committed by Hongchao Deng
co-authored by lnx01
parent c7f9cdcbbf
commit b85dda35f3
19 changed files with 510 additions and 255 deletions
+180 -192
View File
@@ -265,7 +265,7 @@
}
},
"/api/v1/applications/{name}/components/{componentName}": {
"post": {
"get": {
"consumes": [
"application/xml",
"application/json"
@@ -315,7 +315,7 @@
"tags": [
"application"
],
"summary": "deploy or update the application",
"summary": "deploy or upgrade the application",
"operationId": "deployApplication",
"parameters": [
{
@@ -329,7 +329,7 @@
"responses": {
"200": {
"schema": {
"$ref": "#/definitions/v1.ApplicationBase"
"$ref": "#/definitions/v1.ApplicationDeployRequest"
}
},
"400": {
@@ -466,6 +466,57 @@
}
}
},
"put": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"application"
],
"summary": "update policy for application",
"operationId": "updateApplicationPolicy",
"parameters": [
{
"type": "string",
"description": "identifier of the application",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the application policy",
"name": "policyName",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UpdatePolicyRequest"
}
}
],
"responses": {
"200": {
"schema": {
"$ref": "#/definitions/v1.DetailPolicyResponse"
}
},
"400": {
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
},
"delete": {
"consumes": [
"application/xml",
@@ -703,7 +754,7 @@
"namespace"
],
"summary": "list all namespaces",
"operationId": "noop",
"operationId": "listNamespaces",
"responses": {
"200": {
"description": "OK"
@@ -723,7 +774,7 @@
"namespace"
],
"summary": "create namespace",
"operationId": "noop",
"operationId": "createNamespace",
"parameters": [
{
"name": "body",
@@ -741,155 +792,6 @@
}
}
},
"/api/v1/namespaces/{namespace}": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"namespace"
],
"summary": "get one namespace",
"operationId": "noop",
"parameters": [
{
"type": "string",
"description": "identifier of the namespace",
"name": "namespace",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/namespaces/{namespace}/applications/:appname": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"namespace"
],
"summary": "get the specified oam application in the specified namespace",
"operationId": "noop",
"parameters": [
{
"type": "string",
"description": "identifier of the namespace",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the oam application",
"name": "appname",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"post": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"namespace"
],
"summary": "create or update oam application in the specified namespace",
"operationId": "noop",
"parameters": [
{
"type": "string",
"description": "identifier of the namespace",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the oam application",
"name": "appname",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.ApplicationRequest"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"delete": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"namespace"
],
"summary": "create or update oam application in the specified namespace",
"operationId": "noop",
"parameters": [
{
"type": "string",
"description": "identifier of the namespace",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the oam application",
"name": "appname",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/policydefinitions": {
"get": {
"consumes": [
@@ -1399,11 +1301,11 @@
},
"common.AppRolloutStatus": {
"required": [
"rollingState",
"upgradedReadyReplicas",
"batchRollingState",
"currentBatch",
"upgradedReplicas",
"batchRollingState",
"upgradedReadyReplicas",
"rollingState",
"lastTargetAppRevision"
],
"properties": {
@@ -1896,7 +1798,7 @@
"$ref": "#/definitions/common.inputItem"
}
},
"lables": {
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
@@ -2101,6 +2003,54 @@
}
}
},
"v1.ApplicationDeployRequest": {
"required": [
"commit",
"sourceType",
"force"
],
"properties": {
"commit": {
"type": "string"
},
"force": {
"type": "boolean"
},
"sourceType": {
"type": "string"
}
}
},
"v1.ApplicationDeployResponse": {
"required": [
"version",
"status",
"reason",
"deployUser",
"commit",
"sourceType"
],
"properties": {
"commit": {
"type": "string"
},
"deployUser": {
"type": "string"
},
"reason": {
"type": "string"
},
"sourceType": {
"type": "string"
},
"status": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"v1.ApplicationRequest": {
"required": [
"components"
@@ -2502,16 +2452,14 @@
},
"v1.CreateComponentRequest": {
"required": [
"appName",
"name",
"description",
"icon",
"componentType",
"bindClusters"
"bindClusters",
"dependsOn"
],
"properties": {
"appName": {
"type": "string"
},
"bindClusters": {
"type": "array",
"items": {
@@ -2521,9 +2469,18 @@
"componentType": {
"type": "string"
},
"dependsOn": {
"type": "array",
"items": {
"type": "string"
}
},
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"labels": {
"type": "object",
"additionalProperties": {
@@ -2555,10 +2512,14 @@
"v1.CreatePolicyRequest": {
"required": [
"name",
"description",
"type",
"properties"
],
"properties": {
"description": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -2572,12 +2533,12 @@
},
"v1.DetailAddonResponse": {
"required": [
"name",
"version",
"description",
"icon",
"tags",
"phase"
"phase",
"name"
],
"properties": {
"deploy_data": {
@@ -2615,13 +2576,13 @@
"v1.DetailApplicationResponse": {
"required": [
"name",
"updateTime",
"description",
"icon",
"status",
"namespace",
"description",
"createTime",
"gatewayRule",
"namespace",
"createTime",
"updateTime",
"policies",
"status",
"resourceInfo",
@@ -2688,12 +2649,12 @@
},
"v1.DetailClusterResponse": {
"required": [
"name",
"description",
"icon",
"labels",
"status",
"reason",
"name",
"resourceInfo"
],
"properties": {
@@ -2732,11 +2693,11 @@
"v1.DetailComponentResponse": {
"required": [
"createTime",
"updateTime",
"appPrimaryKey",
"name",
"type",
"creator"
"name",
"creator",
"updateTime"
],
"properties": {
"appPrimaryKey": {
@@ -2770,7 +2731,7 @@
"$ref": "#/definitions/common.inputItem"
}
},
"lables": {
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
@@ -2811,17 +2772,25 @@
},
"v1.DetailPolicyResponse": {
"required": [
"name",
"type",
"description",
"creator",
"properties",
"createTime",
"updateTime"
"updateTime",
"name",
"type"
],
"properties": {
"createTime": {
"type": "string",
"format": "date-time"
},
"creator": {
"type": "string"
},
"description": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -2952,13 +2921,13 @@
},
"v1.ListNamespaceResponse": {
"required": [
"namesapces"
"namespaces"
],
"properties": {
"namesapces": {
"namespaces": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.NamesapceBase"
"$ref": "#/definitions/v1.NamespaceBase"
}
}
}
@@ -2994,7 +2963,7 @@
}
}
},
"v1.NamesapceBase": {
"v1.NamespaceBase": {
"required": [
"name",
"description",
@@ -3018,21 +2987,14 @@
}
}
},
"v1.NamesapceDetailResponse": {
"v1.NamespaceDetailResponse": {
"required": [
"name",
"description",
"createTime",
"updateTime",
"clusterBind"
"updateTime"
],
"properties": {
"clusterBind": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"createTime": {
"type": "string",
"format": "date-time"
@@ -3086,6 +3048,8 @@
"required": [
"name",
"type",
"description",
"creator",
"properties",
"createTime",
"updateTime"
@@ -3095,6 +3059,12 @@
"type": "string",
"format": "date-time"
},
"creator": {
"type": "string"
},
"description": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -3131,10 +3101,28 @@
}
}
},
"v1.UpdatePolicyRequest": {
"required": [
"description",
"type",
"properties"
],
"properties": {
"description": {
"type": "string"
},
"properties": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"v1.UpdateWorkflowRequest": {
"required": [
"name",
"namesapce",
"namespace",
"enable"
],
"properties": {
@@ -3144,7 +3132,7 @@
"name": {
"type": "string"
},
"namesapce": {
"namespace": {
"type": "string"
},
"steps": {
+1 -1
View File
@@ -59,7 +59,7 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
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 nil, fmt.Errorf("create namespace failure %w", err)
}
}
return &kubeapi{
@@ -95,14 +95,14 @@ var _ = Describe("Test kubeapi datastore driver", func() {
It("Test batch add funtion", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Namespace: "test-namesapce", Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Namespace: "test-namesapce2", Name: "kubevela-app-4", Description: "this is demo 4"},
&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"},
}
err := kubeStore.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
var datas2 = []datastore.Entity{
&model.Application{Namespace: "test-namesapce", Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Namespace: "test-namespace", 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)
@@ -158,7 +158,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
app.Namespace = "test-namesapce"
app.Namespace = "test-namespace"
list, err = kubeStore.List(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
@@ -63,14 +63,14 @@ var _ = Describe("Test mongodb datastore driver", func() {
It("Test batch add funtion", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Namespace: "test-namesapce", Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Namespace: "test-namesapce2", Name: "kubevela-app-4", Description: "this is demo 4"},
&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"},
}
err := mongodbDriver.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
var datas2 = []datastore.Entity{
&model.Application{Namespace: "test-namesapce", Name: "can-delete", Description: "this is demo can-delete"},
&model.Application{Namespace: "test-namespace", 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)
@@ -112,7 +112,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
app.Namespace = "test-namesapce"
app.Namespace = "test-namespace"
list, err = mongodbDriver.List(context.TODO(), &app, nil)
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
+7 -8
View File
@@ -287,11 +287,11 @@ type ApplicationTemplateVersion struct {
// ListNamespaceResponse namesace list model
type ListNamespaceResponse struct {
Namespaces []NamesapceBase `json:"namesapces"`
Namespaces []NamespaceBase `json:"namespaces"`
}
// NamesapceBase namespace base model
type NamesapceBase struct {
// NamespaceBase namespace base model
type NamespaceBase struct {
Name string `json:"name"`
Description string `json:"description"`
CreateTime time.Time `json:"createTime"`
@@ -304,10 +304,9 @@ type CreateNamespaceRequest struct {
Description string `json:"description"`
}
// NamesapceDetailResponse namespace detail response
type NamesapceDetailResponse struct {
NamesapceBase
ClusterBind map[string]string `json:"clusterBind"`
// NamespaceDetailResponse namespace detail response
type NamespaceDetailResponse struct {
NamespaceBase
}
// ListComponentDefinitionResponse list component dedinition response model
@@ -382,7 +381,7 @@ type PolicyDefinition struct {
// UpdateWorkflowRequest update or create application workflow
type UpdateWorkflowRequest struct {
Name string `json:"name" validate:"checkname"`
Namespace string `json:"namesapce" validate:"checkname"`
Namespace string `json:"namespace" validate:"checkname"`
Steps []WorkflowStep `json:"steps,omitempty"`
Enable bool `json:"enable"`
}
+5 -2
View File
@@ -68,7 +68,10 @@ type applicationUsecaseImpl struct {
// NewApplicationUsecase new application usecase
func NewApplicationUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase) ApplicationUsecase {
kubecli, _ := clients.GetKubeClient()
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
@@ -433,7 +436,7 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
if err := c.kubeClient.Get(ctx, types.NamespacedName{Name: oamApp.Namespace}, &namespace); apierrors.IsNotFound(err) {
namespace.Name = oamApp.Namespace
if err := c.kubeClient.Create(ctx, &namespace); err != nil {
log.Logger.Errorf("auto create namesapce failure %s", err.Error())
log.Logger.Errorf("auto create namespace failure %s", err.Error())
return nil, bcode.ErrCreateNamespace
}
}
+106
View File
@@ -0,0 +1,106 @@
/*
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"
"time"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
// NamespaceUsecase namespace manage usecase.
// Namespace acts as the tenant isolation model on the control side.
type NamespaceUsecase interface {
ListNamespaces(ctx context.Context) ([]apisv1.NamespaceBase, error)
CreateNamespace(ctx context.Context, req apisv1.CreateNamespaceRequest) (*apisv1.NamespaceBase, error)
}
// AnnotationDescription set namespace description in annotation
const AnnotationDescription string = "description"
// LabelCreator set namesapce creator in labels
const LabelCreator string = "creator"
type namespaceUsecaseImpl struct {
kubeClient client.Client
}
// NewNamespaceUsecase new namespace usecase
func NewNamespaceUsecase() NamespaceUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &namespaceUsecaseImpl{kubeClient: kubecli}
}
// ListNamespaces list controller cluster namespaces
func (n *namespaceUsecaseImpl) ListNamespaces(ctx context.Context) ([]apisv1.NamespaceBase, error) {
// TODO: Consider whether to query only namespaces created by Vela
var kubeNamespaces corev1.NamespaceList
if err := n.kubeClient.List(ctx, &kubeNamespaces, &client.ListOptions{}); err != nil {
log.Logger.Errorf("query namespace list from cluster failure %s", err.Error())
return nil, bcode.ErrNamespaceQuery
}
var namespaces []apisv1.NamespaceBase
for _, namesapce := range kubeNamespaces.Items {
namespaces = append(namespaces, apisv1.NamespaceBase{
Name: namesapce.Name,
Description: namesapce.Annotations[AnnotationDescription],
CreateTime: namesapce.CreationTimestamp.Time,
UpdateTime: namesapce.CreationTimestamp.Time,
})
}
return namespaces, nil
}
// CreateNamespace create namespace to controller cluster
func (n *namespaceUsecaseImpl) CreateNamespace(ctx context.Context, req apisv1.CreateNamespaceRequest) (*apisv1.NamespaceBase, error) {
if err := n.kubeClient.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Labels: map[string]string{
LabelCreator: "kubevela",
},
Annotations: map[string]string{
AnnotationDescription: req.Description,
},
},
Spec: corev1.NamespaceSpec{},
}); err != nil {
if apierrors.IsAlreadyExists(err) {
return nil, bcode.ErrNamespaceIsExist
}
return nil, err
}
return &apisv1.NamespaceBase{
Name: req.Name,
Description: req.Description,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}, nil
}
@@ -0,0 +1,50 @@
/*
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/onsi/ginkgo"
. "github.com/onsi/gomega"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
)
var _ = Describe("Test namespace usecase functions", func() {
var (
namespaceUsecase *namespaceUsecaseImpl
)
BeforeEach(func() {
namespaceUsecase = &namespaceUsecaseImpl{kubeClient: k8sClient}
})
It("Test CreateNamespace function", func() {
req := apisv1.CreateNamespaceRequest{
Name: "test-namespace",
Description: "this is a namespace description 王二",
}
base, err := namespaceUsecase.CreateNamespace(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
})
It("Test ListNamespace function", func() {
_, err := namespaceUsecase.ListNamespaces(context.TODO())
Expect(err).Should(BeNil())
})
})
@@ -0,0 +1,23 @@
/*
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
// ErrNamespaceQuery query namespace failure from k8s api
var ErrNamespaceQuery = NewBcode(500, 30001, "query namespace list from cluster failure")
// ErrNamespaceIsExist namespace name is exist
var ErrNamespaceIsExist = NewBcode(400, 30002, "namespace name is exist")
+51 -31
View File
@@ -20,13 +20,22 @@ import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "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/bcode"
)
type namespaceWebService struct {
namespaceUsecase usecase.NamespaceUsecase
}
func (c *namespaceWebService) GetWebService() *restful.WebService {
// NewNamespaceWebService new namespace webservice
func NewNamespaceWebService(namespaceUsecase usecase.NamespaceUsecase) WebService {
return &namespaceWebService{namespaceUsecase: namespaceUsecase}
}
func (n *namespaceWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/namespaces").
Consumes(restful.MIME_XML, restful.MIME_JSON).
@@ -35,42 +44,53 @@ func (c *namespaceWebService) GetWebService() *restful.WebService {
tags := []string{"namespace"}
ws.Route(ws.GET("/").To(noop).
ws.Route(ws.GET("/").To(n.listNamespaces).
Doc("list all namespaces").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(apis.ListNamespaceResponse{}))
ws.Route(ws.POST("/").To(noop).
ws.Route(ws.POST("/").To(n.createNamespace).
Doc("create namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateNamespaceRequest{}).
Writes(apis.NamesapceDetailResponse{}))
ws.Route(ws.GET("/{namespace}").To(noop).
Doc("get one namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Writes(apis.NamesapceDetailResponse{}))
// Compatible with historical apis
ws.Route(ws.GET("/{namespace}/applications/:appname").To(noop).
Doc("get the specified oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")).
Writes(apis.ApplicationResponse{}))
ws.Route(ws.POST("/{namespace}/applications/:appname").To(noop).
Doc("create or update oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")).
Reads(apis.ApplicationRequest{}))
ws.Route(ws.DELETE("/{namespace}/applications/:appname").To(noop).
Doc("create or update oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")))
Writes(apis.NamespaceDetailResponse{}))
return ws
}
func (n *namespaceWebService) listNamespaces(req *restful.Request, res *restful.Response) {
namespaces, err := n.namespaceUsecase.ListNamespaces(req.Request.Context())
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.ListNamespaceResponse{Namespaces: namespaces}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *namespaceWebService) createNamespace(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateNamespaceRequest
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
namespaceBase, err := n.namespaceUsecase.CreateNamespace(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(apis.NamespaceDetailResponse{NamespaceBase: *namespaceBase}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
@@ -29,26 +29,26 @@ var _ = Describe("Test validate function", func() {
Expect(cmp.Diff(nameRegexp.MatchString("///Asd asda "), false)).Should(BeEmpty())
var app0 = apisv1.CreateApplicationRequest{
Name: "a",
Namespace: "namesapce",
Namespace: "namespace",
}
err := validate.Struct(&app0)
Expect(err).ShouldNot(BeNil())
var app1 = apisv1.CreateApplicationRequest{
Name: "Asdasd",
Namespace: "namesapce",
Namespace: "namespace",
}
err = validate.Struct(&app1)
Expect(err).ShouldNot(BeNil())
var app2 = apisv1.CreateApplicationRequest{
Name: "asdasd asdasd ++",
Namespace: "namesapce",
Namespace: "namespace",
}
err = validate.Struct(&app2)
Expect(err).ShouldNot(BeNil())
var app3 = apisv1.CreateApplicationRequest{
Name: "asdasd",
Namespace: "namesapce",
Namespace: "namespace",
}
err = validate.Struct(&app3)
Expect(err).Should(BeNil())
+2 -1
View File
@@ -62,10 +62,11 @@ func Init(ctx context.Context, ds datastore.DataStore) {
clusterUsecase := usecase.NewClusterUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase)
namespaceUsecase := usecase.NewNamespaceUsecase()
oamApplicationUsecase := usecase.NewOAMApplicationUsecase()
RegistWebService(NewClusterWebService(clusterUsecase))
RegistWebService(NewApplicationWebService(applicationUsecase))
RegistWebService(&namespaceWebService{})
RegistWebService(NewNamespaceWebService(namespaceUsecase))
RegistWebService(&componentDefinitionWebservice{})
RegistWebService(&addonWebService{})
RegistWebService(NewOAMApplication(oamApplicationUsecase))
@@ -209,7 +209,7 @@ func (a *AppManifestsDispatcher) retrieveLegacyResourceTrackers(ctx context.Cont
oldRtList := &v1beta1.ResourceTrackerList{}
if err := a.c.List(ctx, oldRtList, client.MatchingLabels{
oam.LabelAppName: ExtractAppName(a.currentRTName, a.namespace),
"app.oam.dev/namesapce": a.namespace,
"app.oam.dev/namespace": a.namespace,
}); err != nil {
return errors.Wrap(err, "cannot retrieve legacy resource trackers with miss-spell label")
}
@@ -490,7 +490,7 @@ var _ = Describe("Test compatibility code", func() {
ObjectMeta: metav1.ObjectMeta{
Name: appName + "-v2-" + namespaceName,
Labels: map[string]string{
"app.oam.dev/namesapce": namespaceName,
"app.oam.dev/namespace": namespaceName,
oam.LabelAppName: appName,
},
},
@@ -245,7 +245,7 @@ func NewFakeRecorder(bufferSize int) *FakeRecorder {
// randomNamespaceName generates a random name based on the basic name.
// Running each ginkgo case in a new namespace with a random name can avoid
// waiting a long time to GC namesapce.
// waiting a long time to GC namespace.
func randomNamespaceName(basic string) string {
return fmt.Sprintf("%s-%s", basic, strconv.FormatInt(rand.Int63(), 16))
}
+1 -1
View File
@@ -1338,7 +1338,7 @@ func TestGetDefinitionWithClusterScope(t *testing.T) {
},
},
}
// old cluster workload trait scope definition crd is cluster scope, the namesapce field is empty
// old cluster workload trait scope definition crd is cluster scope, the namespace field is empty
noNs := v1alpha2.TraitDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "noNsDefinition",
+3 -3
View File
@@ -38,7 +38,7 @@ var _ = Describe("Test application rest api", func() {
defer GinkgoRecover()
var req = apisv1.CreateApplicationRequest{
Name: "test-app-sadasd",
Namespace: "test-app-namesapce",
Namespace: "test-app-namespace",
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
@@ -77,7 +77,7 @@ var _ = Describe("Test application rest api", func() {
Expect(err).Should(Succeed())
var req = apisv1.CreateApplicationRequest{
Name: "test-app-sadasd",
Namespace: "test-app-namesapce",
Namespace: "test-app-namespace",
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
@@ -177,7 +177,7 @@ var _ = Describe("Test application rest api", func() {
Expect(cmp.Diff(response.Status, model.DeployEventRunning)).Should(BeEmpty())
var oam v1beta1.Application
err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: "test-app-sadasd", Namespace: "test-app-namesapce"}, &oam)
err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: "test-app-sadasd", Namespace: "test-app-namespace"}, &oam)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(oam.Spec.Components), 2)).Should(BeEmpty())
Expect(cmp.Diff(len(oam.Spec.Policies), 1)).Should(BeEmpty())
+65
View File
@@ -0,0 +1,65 @@
/*
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 namespace rest api", func() {
It("Test create namespace", func() {
defer GinkgoRecover()
var req = apisv1.CreateNamespaceRequest{
Name: "dev-team",
Description: "开发环境租户",
}
bodyByte, err := json.Marshal(req)
Expect(err).ShouldNot(HaveOccurred())
res, err := http.Post("http://127.0.0.1:8000/api/v1/namespaces", "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 namespaceBase apisv1.NamespaceBase
err = json.NewDecoder(res.Body).Decode(&namespaceBase)
Expect(err).ShouldNot(HaveOccurred())
Expect(cmp.Diff(namespaceBase.Name, req.Name)).Should(BeEmpty())
Expect(cmp.Diff(namespaceBase.Description, req.Description)).Should(BeEmpty())
})
It("Test list namespace", func() {
defer GinkgoRecover()
res, err := http.Get("http://127.0.0.1:8000/api/v1/namespaces")
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 namespaces apisv1.ListNamespaceResponse
err = json.NewDecoder(res.Body).Decode(&namespaces)
Expect(err).ShouldNot(HaveOccurred())
})
})
+1 -1
View File
@@ -302,7 +302,7 @@ func RequestReconcileNow(ctx context.Context, o client.Object) {
// randomNamespaceName generates a random name based on the basic name.
// Running each ginkgo case in a new namespace with a random name can avoid
// waiting a long time to GC namesapce.
// waiting a long time to GC namespace.
func randomNamespaceName(basic string) string {
return fmt.Sprintf("%s-%s", basic, strconv.FormatInt(rand.Int63(), 16))
}