diff --git a/docs/apidoc/swagger.json b/docs/apidoc/swagger.json index ed9cab8f6..6ec5ec9fd 100644 --- a/docs/apidoc/swagger.json +++ b/docs/apidoc/swagger.json @@ -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": { diff --git a/pkg/apiserver/datastore/kubeapi/kubeapi.go b/pkg/apiserver/datastore/kubeapi/kubeapi.go index fa7dfab61..94a35cfdb 100644 --- a/pkg/apiserver/datastore/kubeapi/kubeapi.go +++ b/pkg/apiserver/datastore/kubeapi/kubeapi.go @@ -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{ diff --git a/pkg/apiserver/datastore/kubeapi/kubeapi_test.go b/pkg/apiserver/datastore/kubeapi/kubeapi_test.go index 60778d86e..c4840db32 100644 --- a/pkg/apiserver/datastore/kubeapi/kubeapi_test.go +++ b/pkg/apiserver/datastore/kubeapi/kubeapi_test.go @@ -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) diff --git a/pkg/apiserver/datastore/mongodb/mongodb_test.go b/pkg/apiserver/datastore/mongodb/mongodb_test.go index 19a53c9d9..82a326079 100644 --- a/pkg/apiserver/datastore/mongodb/mongodb_test.go +++ b/pkg/apiserver/datastore/mongodb/mongodb_test.go @@ -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) diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go index 755227472..19f6d0ac4 100644 --- a/pkg/apiserver/rest/apis/v1/types.go +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -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"` } diff --git a/pkg/apiserver/rest/usecase/application.go b/pkg/apiserver/rest/usecase/application.go index e1c7ed991..f6bd53e81 100644 --- a/pkg/apiserver/rest/usecase/application.go +++ b/pkg/apiserver/rest/usecase/application.go @@ -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 } } diff --git a/pkg/apiserver/rest/usecase/namespace.go b/pkg/apiserver/rest/usecase/namespace.go new file mode 100644 index 000000000..7f7fdf4a1 --- /dev/null +++ b/pkg/apiserver/rest/usecase/namespace.go @@ -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 +} diff --git a/pkg/apiserver/rest/usecase/namespace_test.go b/pkg/apiserver/rest/usecase/namespace_test.go new file mode 100644 index 000000000..ff6e11d2b --- /dev/null +++ b/pkg/apiserver/rest/usecase/namespace_test.go @@ -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()) + }) +}) diff --git a/pkg/apiserver/rest/utils/bcode/namespace.go b/pkg/apiserver/rest/utils/bcode/namespace.go new file mode 100644 index 000000000..d9145f9c9 --- /dev/null +++ b/pkg/apiserver/rest/utils/bcode/namespace.go @@ -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") diff --git a/pkg/apiserver/rest/webservice/namespace.go b/pkg/apiserver/rest/webservice/namespace.go index 31af705dc..46a5ccf2b 100644 --- a/pkg/apiserver/rest/webservice/namespace.go +++ b/pkg/apiserver/rest/webservice/namespace.go @@ -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 + } +} diff --git a/pkg/apiserver/rest/webservice/validate_test.go b/pkg/apiserver/rest/webservice/validate_test.go index f6fd675b7..7b71fab51 100644 --- a/pkg/apiserver/rest/webservice/validate_test.go +++ b/pkg/apiserver/rest/webservice/validate_test.go @@ -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()) diff --git a/pkg/apiserver/rest/webservice/webservice.go b/pkg/apiserver/rest/webservice/webservice.go index 8681550bf..7c03cd777 100644 --- a/pkg/apiserver/rest/webservice/webservice.go +++ b/pkg/apiserver/rest/webservice/webservice.go @@ -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)) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch.go b/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch.go index 6ff29d2f8..fd99f856d 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch.go @@ -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") } diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch_suite_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch_suite_test.go index 8eabf6398..6109988a7 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch_suite_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/dispatch/dispatch_suite_test.go @@ -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, }, }, diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go index 584f9ed20..d00bf6af0 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go @@ -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)) } diff --git a/pkg/oam/util/helper_test.go b/pkg/oam/util/helper_test.go index bb9dfc3bc..8d35c6de3 100644 --- a/pkg/oam/util/helper_test.go +++ b/pkg/oam/util/helper_test.go @@ -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", diff --git a/test/e2e-apiserver-test/application_test.go b/test/e2e-apiserver-test/application_test.go index c6d67393b..36a76c9b2 100644 --- a/test/e2e-apiserver-test/application_test.go +++ b/test/e2e-apiserver-test/application_test.go @@ -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()) diff --git a/test/e2e-apiserver-test/namespace_test.go b/test/e2e-apiserver-test/namespace_test.go new file mode 100644 index 000000000..6fa313610 --- /dev/null +++ b/test/e2e-apiserver-test/namespace_test.go @@ -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()) + }) +}) diff --git a/test/e2e-test/suite_test.go b/test/e2e-test/suite_test.go index 0b893d533..43ac3b3d8 100644 --- a/test/e2e-test/suite_test.go +++ b/test/e2e-test/suite_test.go @@ -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)) }