mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-23 22:46:53 +00:00
add create or update app apiserver (#2087)
* add create or update app * add apiserver test
This commit is contained in:
@@ -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 apis
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
// ApplicationRequest represents application request for APIServer
|
||||
type ApplicationRequest struct {
|
||||
Components []common.ApplicationComponent `json:"components"`
|
||||
Policies []v1beta1.AppPolicy `json:"policies,omitempty"`
|
||||
Workflow *v1beta1.Workflow `json:"workflow,omitempty"`
|
||||
}
|
||||
@@ -91,13 +91,22 @@ func (s *restServer) Run(ctx context.Context) error {
|
||||
|
||||
func (s *restServer) registerServices() error {
|
||||
|
||||
/* ************************************************************** */
|
||||
/* ************* Open API Route Group ***************** */
|
||||
/* ************************************************************** */
|
||||
openapi := s.server.Group("/v1")
|
||||
|
||||
// catalog
|
||||
catalogService := services.NewCatalogService(s.k8sClient)
|
||||
s.server.GET("/catalogs", catalogService.ListCatalogs)
|
||||
s.server.POST("/catalogs", catalogService.AddCatalog)
|
||||
s.server.PUT("/catalogs", catalogService.UpdateCatalog)
|
||||
s.server.GET("/catalogs/:catalogName", catalogService.GetCatalog)
|
||||
s.server.DELETE("/catalogs/:catalogName", catalogService.DelCatalog)
|
||||
openapi.GET("/catalogs", catalogService.ListCatalogs)
|
||||
openapi.POST("/catalogs", catalogService.AddCatalog)
|
||||
openapi.PUT("/catalogs", catalogService.UpdateCatalog)
|
||||
openapi.GET("/catalogs/:catalogName", catalogService.GetCatalog)
|
||||
openapi.DELETE("/catalogs/:catalogName", catalogService.DelCatalog)
|
||||
|
||||
// application
|
||||
applicationService := services.NewApplicationService(s.k8sClient)
|
||||
openapi.POST("/namespaces/:namespace/applications/:appname", applicationService.CreateOrUpdateApplication)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/apis"
|
||||
)
|
||||
|
||||
// ApplicationService serves as Application Open API for request
|
||||
type ApplicationService struct {
|
||||
k8sClient client.Client
|
||||
}
|
||||
|
||||
// NewApplicationService create an application service
|
||||
func NewApplicationService(kc client.Client) *ApplicationService {
|
||||
return &ApplicationService{
|
||||
k8sClient: kc,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrUpdateApplication will create or update application
|
||||
// POST /v1/namespaces/<namespace>/applications/<appname>
|
||||
func (s *ApplicationService) CreateOrUpdateApplication(c echo.Context) error {
|
||||
namespace := c.Param("namespace")
|
||||
name := c.Param("appname")
|
||||
appReq := new(apis.ApplicationRequest)
|
||||
if err := c.Bind(appReq); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body: " + err.Error()})
|
||||
}
|
||||
ctx := context.TODO()
|
||||
var app, existApp v1beta1.Application
|
||||
app.Namespace = namespace
|
||||
app.Name = name
|
||||
app.Spec.Components = appReq.Components
|
||||
app.Spec.Policies = appReq.Policies
|
||||
app.Spec.Workflow = appReq.Workflow
|
||||
err := s.k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, &existApp)
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to get application: " + err.Error()})
|
||||
}
|
||||
err = s.k8sClient.Create(ctx, &app)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to create application: " + err.Error()})
|
||||
}
|
||||
return c.JSON(http.StatusOK, struct{}{})
|
||||
}
|
||||
existApp.Spec = app.Spec
|
||||
err = s.k8sClient.Update(ctx, &existApp)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to update application: " + err.Error()})
|
||||
}
|
||||
return c.JSON(http.StatusOK, struct{}{})
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
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 services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/apis"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestApplicationCreateOrUpdate(t *testing.T) {
|
||||
cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
|
||||
appSvc := NewApplicationService(cw)
|
||||
|
||||
appComp1 := common2.ApplicationComponent{
|
||||
Name: "mycomp",
|
||||
Type: "webservice",
|
||||
Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v1"}`)},
|
||||
}
|
||||
appComp2 := common2.ApplicationComponent{
|
||||
Name: "mycomp2",
|
||||
Type: "webservice",
|
||||
Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v2"}`)},
|
||||
}
|
||||
tests := map[string]struct {
|
||||
appReq *apis.ApplicationRequest
|
||||
rawReq []byte
|
||||
name string
|
||||
namespace string
|
||||
expHttpCode int
|
||||
expErr string
|
||||
expApp *v1beta1.Application
|
||||
}{
|
||||
"normal create with only component": {
|
||||
appReq: &apis.ApplicationRequest{
|
||||
Components: []common2.ApplicationComponent{appComp1},
|
||||
},
|
||||
expHttpCode: 200,
|
||||
name: "myapp",
|
||||
namespace: "mynamespace",
|
||||
expApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common2.ApplicationComponent{appComp1},
|
||||
},
|
||||
},
|
||||
},
|
||||
"create with bind error": {
|
||||
rawReq: []byte("XXXX"),
|
||||
expHttpCode: 400,
|
||||
name: "myapp",
|
||||
namespace: "mynamespace",
|
||||
expErr: "invalid request body: code=400",
|
||||
},
|
||||
"normal update with component and trait": {
|
||||
appReq: &apis.ApplicationRequest{
|
||||
Components: []common2.ApplicationComponent{appComp1, appComp2},
|
||||
},
|
||||
expHttpCode: 200,
|
||||
name: "myapp",
|
||||
namespace: "mynamespace",
|
||||
expApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common2.ApplicationComponent{appComp1, appComp2},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for casename, c := range tests {
|
||||
var err error
|
||||
if c.appReq != nil {
|
||||
c.rawReq, err = json.Marshal(c.appReq)
|
||||
assert.NoError(t, err, casename)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(c.rawReq))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
rec := httptest.NewRecorder()
|
||||
echoCtx := echo.New().NewContext(req, rec)
|
||||
echoCtx.SetParamNames("namespace", "appname")
|
||||
echoCtx.SetParamValues(c.namespace, c.name)
|
||||
|
||||
err = appSvc.CreateOrUpdateApplication(echoCtx)
|
||||
assert.NoError(t, err, casename)
|
||||
|
||||
// check response
|
||||
assert.Equal(t, c.expHttpCode, rec.Code, casename)
|
||||
gotResp := map[string]string{}
|
||||
err = json.Unmarshal(rec.Body.Bytes(), &gotResp)
|
||||
assert.NoError(t, err, casename)
|
||||
if c.expErr != "" {
|
||||
assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename)
|
||||
}
|
||||
|
||||
if len(c.expErr) > 0 {
|
||||
continue
|
||||
}
|
||||
var appObj v1beta1.Application
|
||||
err = cw.Get(context.TODO(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, &appObj)
|
||||
assert.NoError(t, err, casename)
|
||||
assert.Equal(t, c.expApp.Spec, appObj.Spec, casename)
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ var _ = Describe("Test Catalog Service", func() {
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
c.SetPath("/catalogs/:catalogName")
|
||||
c.SetPath("/v1/catalogs/:catalogName")
|
||||
c.SetParamNames("catalogName")
|
||||
c.SetParamValues(cr.Name)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user