diff --git a/pkg/apiserver/rest/apis/core.go b/pkg/apiserver/rest/apis/core.go index 8caaa807a..a340f076a 100644 --- a/pkg/apiserver/rest/apis/core.go +++ b/pkg/apiserver/rest/apis/core.go @@ -27,3 +27,11 @@ type ApplicationRequest struct { Policies []v1beta1.AppPolicy `json:"policies,omitempty"` Workflow *v1beta1.Workflow `json:"workflow,omitempty"` } + +// ApplicationResponse represents application response for APIServer +type ApplicationResponse struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Spec v1beta1.ApplicationSpec `json:"spec"` + Status common.AppStatus `json:"status"` +} diff --git a/pkg/apiserver/rest/rest_server.go b/pkg/apiserver/rest/rest_server.go index fc3477f24..5b65bf714 100644 --- a/pkg/apiserver/rest/rest_server.go +++ b/pkg/apiserver/rest/rest_server.go @@ -106,7 +106,9 @@ func (s *restServer) registerServices() error { // application applicationService := services.NewApplicationService(s.k8sClient) + openapi.GET("/namespaces/:namespace/applications/:appname", applicationService.GetApplication) openapi.POST("/namespaces/:namespace/applications/:appname", applicationService.CreateOrUpdateApplication) + openapi.DELETE("/namespaces/:namespace/applications/:appname", applicationService.DeleteApplication) return nil } diff --git a/pkg/apiserver/rest/services/application.go b/pkg/apiserver/rest/services/application.go index 084e64733..346e23d1c 100644 --- a/pkg/apiserver/rest/services/application.go +++ b/pkg/apiserver/rest/services/application.go @@ -40,8 +40,34 @@ func NewApplicationService(kc client.Client) *ApplicationService { } } +// GetApplication will get application status +// GET /namespaces//applications/ +func (s *ApplicationService) GetApplication(c echo.Context) error { + namespace := c.Param("namespace") + appName := c.Param("appname") + + ctx := context.TODO() + var existApp v1beta1.Application + err := s.k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appName}, &existApp) + if err != nil { + if apierrors.IsNotFound(err) { + return c.JSON(http.StatusNotFound, map[string]string{"error": "application does not exist: " + err.Error()}) + } + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to get application: " + err.Error()}) + } + + var appResp = &apis.ApplicationResponse{ + APIVersion: existApp.APIVersion, + Kind: existApp.Kind, + Spec: existApp.Spec, + Status: existApp.Status, + } + + return c.JSON(http.StatusOK, appResp) +} + // CreateOrUpdateApplication will create or update application -// POST /v1/namespaces//applications/ +// POST /namespaces//applications/ func (s *ApplicationService) CreateOrUpdateApplication(c echo.Context) error { namespace := c.Param("namespace") name := c.Param("appname") @@ -74,3 +100,23 @@ func (s *ApplicationService) CreateOrUpdateApplication(c echo.Context) error { } return c.JSON(http.StatusOK, struct{}{}) } + +// DeleteApplication will delete application +// delete /v1/namespaces//applications/ +func (s *ApplicationService) DeleteApplication(c echo.Context) error { + namespace := c.Param("namespace") + appName := c.Param("appname") + + ctx := context.TODO() + var existApp v1beta1.Application + existApp.Namespace = namespace + existApp.Name = appName + err := s.k8sClient.Delete(ctx, &existApp) + if err != nil { + if apierrors.IsNotFound(err) { + return c.JSON(http.StatusNotFound, map[string]string{"error": "application does not exist: " + err.Error()}) + } + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to delete application: " + err.Error()}) + } + return c.JSON(http.StatusOK, struct{}{}) +} diff --git a/pkg/apiserver/rest/services/application_test.go b/pkg/apiserver/rest/services/application_test.go index e84ef632f..466e86b30 100644 --- a/pkg/apiserver/rest/services/application_test.go +++ b/pkg/apiserver/rest/services/application_test.go @@ -27,6 +27,7 @@ import ( "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -112,19 +113,160 @@ func TestApplicationCreateOrUpdate(t *testing.T) { // 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 != "" { + if c.expErr != "" { // compare return error with map type + gotResp := map[string]string{} + err = json.Unmarshal(rec.Body.Bytes(), &gotResp) + assert.NoError(t, err, casename) assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) + } else { // check app spec in fake cluster + 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) } - - 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) } } + +func TestApplicationGet(t *testing.T) { + cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build() + appSvc := NewApplicationService(cw) + + tests := map[string]struct { + rawReq []byte + name string + namespace string + expHttpCode int + expErr string + expApp *v1beta1.Application + }{ + "normal get method for application": { + expHttpCode: 200, + name: "commonName", + namespace: "commonNamespace", + }, + "get app failed with resource not found": { + expHttpCode: 404, + name: "notExistName", + namespace: "commonNamespace", + expErr: "application does not exist", + }, + } + // create an application for get + createAppForTest(t, appSvc) + + for casename, c := range tests { + var err error + req := httptest.NewRequest(http.MethodGet, "/", 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.GetApplication(echoCtx) + assert.NoError(t, err, casename) + + // check response + assert.Equal(t, c.expHttpCode, rec.Code, casename) + if c.expErr != "" { // compare return error with map type + gotResp := map[string]string{} + err = json.Unmarshal(rec.Body.Bytes(), &gotResp) + assert.NoError(t, err, casename) + assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) + } else { // check app spec in fake cluster + var gotResp apis.ApplicationResponse + err = json.Unmarshal(rec.Body.Bytes(), &gotResp) + assert.NoError(t, err, casename) + + 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, gotResp.APIVersion, appObj.APIVersion, casename) + assert.Equal(t, gotResp.Kind, appObj.Kind, casename) + assert.Equal(t, gotResp.Spec, appObj.Spec, casename) + assert.Equal(t, gotResp.Status, appObj.Status, casename) + } + + } +} + +func TestApplicationDelete(t *testing.T) { + cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build() + appSvc := NewApplicationService(cw) + + tests := map[string]struct { + rawReq []byte + name string + namespace string + expHttpCode int + expErr string + }{ + "normal delete method for application": { + expHttpCode: 200, + name: "commonName", + namespace: "commonNamespace", + }, + "delete app failed with resource not found": { + expHttpCode: 404, + name: "notExistName", + namespace: "commonNamespace", + expErr: "application does not exist", + }, + } + for casename, c := range tests { + // create common app + createAppForTest(t, appSvc) + var err error + req := httptest.NewRequest(http.MethodDelete, "/", 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.DeleteApplication(echoCtx) + assert.NoError(t, err, casename) + + // check response + assert.Equal(t, c.expHttpCode, rec.Code, casename) + if c.expErr != "" { + gotResp := map[string]string{} + err = json.Unmarshal(rec.Body.Bytes(), &gotResp) + assert.NoError(t, err, casename) + assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) + } else { + // checkout app status in fake cluster + var appObj v1beta1.Application + err = cw.Get(context.TODO(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, &appObj) + assert.Equal(t, apierrors.IsNotFound(err), true) + } + } +} + +func createAppForTest(t *testing.T, appSvc *ApplicationService) { + appComp := common2.ApplicationComponent{ + Name: "mycomp", + Type: "webservice", + Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v1"}`)}, + } + + var appReq = &apis.ApplicationRequest{ + Components: []common2.ApplicationComponent{appComp}, + } + + var rawReq []byte + var err error + if appReq != nil { + rawReq, err = json.Marshal(appReq) + assert.NoError(t, err, "marshal request for create app") + } + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(rawReq)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + echoCtx := echo.New().NewContext(req, rec) + echoCtx.SetParamNames("namespace", "appname") + echoCtx.SetParamValues("commonNamespace", "commonName") + + err = appSvc.CreateOrUpdateApplication(echoCtx) + assert.NoError(t, err, "craete application in service") +}