Jaycean commented on a change in pull request #1245:
URL: https://github.com/apache/apisix-dashboard/pull/1245#discussion_r555623623



##########
File path: api/internal/handler/route_export/route_export.go
##########
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 route_export
+
+import (
+       "encoding/json"
+       "reflect"
+       "regexp"
+       "strings"
+
+       "github.com/apisix/manager-api/internal/core/entity"
+       "github.com/apisix/manager-api/internal/core/store"
+       "github.com/apisix/manager-api/internal/handler"
+       "github.com/apisix/manager-api/internal/log"
+       "github.com/getkin/kin-openapi/openapi3"
+       "github.com/gin-gonic/gin"
+       "github.com/shiningrush/droplet"
+       "github.com/shiningrush/droplet/wrapper"
+       wgin "github.com/shiningrush/droplet/wrapper/gin"
+)
+
+type Handler struct {
+       routeStore store.Interface
+}
+
+func NewHandler() (handler.RouteRegister, error) {
+       return &Handler{
+               routeStore: store.GetStore(store.HubKeyRoute),
+       }, nil
+}
+
+func (h *Handler) ApplyRoute(r *gin.Engine) {
+       r.POST("/apisix/admin/routes/export/:ids", wgin.Wraps(h.ExportRoutes,
+               wrapper.InputType(reflect.TypeOf(ExportInput{}))))
+}
+
+type ExportInput struct {
+       IDs string `auto_read:"ids,path"`
+}
+
+func (h *Handler) ExportRoutes(c droplet.Context) (interface{}, error) {
+       input := c.Input().(*ExportInput)
+       ids := strings.Split(input.IDs, ",")
+       routes := []*entity.Route{}
+       for _, id := range ids {
+               route, err := h.routeStore.Get(id)
+               if err != nil {
+                       return nil, err
+               }
+               routes = append(routes, route.(*entity.Route))
+
+       }
+       return routeToOpenApi3(routes), nil
+}
+
+type AuthType string
+
+const (
+       BasicAuth AuthType = "basic-auth"
+       KeyAuth   AuthType = "key-auth"
+       JWTAuth   AuthType = "jwt-auth"
+)
+
+var (
+       openApi = "3.0.0"
+       title   = "Routes Export"
+)
+
+func routeToOpenApi3(routes []*entity.Route) *openapi3.Swagger {
+       paths := openapi3.Paths{}
+       pathItem := &openapi3.PathItem{}
+       path := &openapi3.Operation{}
+       paramsRefs := []*openapi3.ParameterRef{}
+       requestBody := &openapi3.RequestBody{}
+       components := &openapi3.Components{}
+       secSchemas := openapi3.SecuritySchemes{}
+       for _, route := range routes {
+
+               extensions := make(map[string]interface{})
+               path.Summary = route.Desc
+               path.OperationID = route.Name
+               if route.Upstream != nil {
+                       extensions["x-apisix-upstream"] = route.Upstream
+               }
+               if route.Host != "" {
+                       extensions["x-apisix-host"] = route.Host
+               }
+               if route.Hosts != nil {
+                       extensions["x-apisix-hosts"] = route.Hosts
+               }
+               if route.Labels != nil {
+                       extensions["x-apisix-labels"] = route.Labels
+               }
+               if route.RemoteAddr != "" {
+                       extensions["x-apisix-remoteAddr"] = route.RemoteAddr
+               }
+               if route.RemoteAddrs != nil {
+                       extensions["x-apisix-remoteAddrs"] = route.RemoteAddrs
+               }
+               if route.FilterFunc != "" {
+                       extensions["x-apisix-filterFunc"] = route.FilterFunc
+               }
+               if route.ServiceID != nil {
+                       extensions["x-apisix-serviceID"] = route.ServiceID
+               }
+               if route.UpstreamID != nil {
+                       extensions["x-apisix-upstreamID"] = route.UpstreamID
+               }
+               if route.Script != nil {
+                       extensions["x-apisix-script"] = route.Script
+               }
+               if route.ServiceProtocol != "" {
+                       extensions["x-apisix-serviceProtocol"] = 
route.ServiceProtocol
+               }
+               if route.Vars != nil {
+                       extensions["x-apisix-vars"] = route.Vars
+               }
+
+               // analysis route.URIs
+               routeURIs := []string{}
+               if route.URI != "" {
+                       routeURIs = append(routeURIs, route.URI)
+               }
+               if route.Uris != nil {
+                       routeURIs = route.Uris
+               }
+
+               for _, uri := range routeURIs {
+                       if strings.Contains(uri, "*") {
+                               paths[strings.Split(uri, "*")[0]+"{params}"] = 
pathItem
+                               // add params introduce
+                               paramsRefs = append(paramsRefs, 
&openapi3.ParameterRef{
+                                       Value: &openapi3.Parameter{
+                                               In:          "path",
+                                               Name:        "params",
+                                               Required:    true,
+                                               Description: "params in path",
+                                               Schema:      
&openapi3.SchemaRef{Value: &openapi3.Schema{Type: "string"}}}})
+                       } else {
+                               paths[uri] = pathItem
+                       }
+               }
+
+               if route.Plugins != nil {
+                       param := &openapi3.Parameter{}
+                       secReq := &openapi3.SecurityRequirements{}
+                       plugins := make(map[string]interface{})
+                       // analysis plugins
+                       for key, value := range route.Plugins {
+                               // analysis proxy-rewrite plugin
+                               //if key == "proxy-rewrite" {
+                               //      continue
+                               //}
+
+                               // analysis request-validation plugin
+                               if key == "request-validation" {
+                                       if valueMap, ok := 
value.(map[string]interface{}); ok {
+                                               if hsVal, ok := 
valueMap["header_schema"]; ok {
+                                                       param.In = "header"
+                                                       requestValidation := 
&entity.RequestValidation{}
+                                                       reqBytes, _ := 
json.Marshal(&hsVal)
+                                                       err := 
json.Unmarshal(reqBytes, requestValidation)
+                                                       if err != nil {
+                                                               
log.Errorf("json marshal failed: %s", err)
+                                                       }
+                                                       for key1, value1 := 
range requestValidation.Properties.(map[string]interface{}) {
+                                                               for _, arr := 
range requestValidation.Required {
+                                                                       if arr 
== key1 {
+                                                                               
param.Required = true
+                                                                       }
+                                                               }
+                                                               param.Name = 
key1
+                                                               typeStr := 
value1.(map[string]interface{})
+                                                               schema := 
&openapi3.Schema{Type: typeStr["type"].(string)}
+                                                               param.Schema = 
&openapi3.SchemaRef{Value: schema}
+                                                               paramsRefs = 
append(paramsRefs, &openapi3.ParameterRef{Value: param})
+                                                       }
+                                               }
+
+                                               if bsVal, ok := 
valueMap["body_schema"]; ok {
+                                                       m := 
map[string]*openapi3.MediaType{}
+                                                       reqBytes, _ := 
json.Marshal(&bsVal)
+                                                       schema := 
&openapi3.Schema{}
+                                                       err := 
json.Unmarshal(reqBytes, schema)
+                                                       if err != nil {
+                                                               
log.Errorf("json marshal failed: %s", err)
+                                                       }
+                                                       for _, va := range 
route.Vars.([]interface{}) {
+                                                               compile := 
regexp.MustCompile("^http_.*")
+                                                               match := 
compile.Match([]byte(va.([]interface{})[0].(string)))
+                                                               if match {
+                                                                       
m[va.([]interface{})[2].(string)] = &openapi3.MediaType{Schema: 
&openapi3.SchemaRef{Value: schema}}
+                                                                       
requestBody.Content = m
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       continue
+                               }
+                               // analysis security plugins
+                               securityEnv := &openapi3.SecurityRequirement{}
+                               switch key {
+                               case string(KeyAuth):
+                                       secSchemas["api_key"] = 
&openapi3.SecuritySchemeRef{Value: openapi3.NewCSRFSecurityScheme()}
+                                       securityEnv.Authenticate("api_key", " ")
+                                       secReq.With(*securityEnv)
+                                       continue
+                               case string(BasicAuth):
+                                       secSchemas["basicAuth"] = 
&openapi3.SecuritySchemeRef{Value: &openapi3.SecurityScheme{
+                                               Type: "basicAuth",
+                                               Name: "basicAuth",
+                                               In:   "header",
+                                       }}
+                                       securityEnv.Authenticate("basicAuth", " 
")
+                                       secReq.With(*securityEnv)
+                                       continue
+                               case string(JWTAuth):
+                                       secSchemas["bearerAuth"] = 
&openapi3.SecuritySchemeRef{Value: openapi3.NewJWTSecurityScheme()}
+                                       securityEnv.Authenticate("bearerAuth", 
" ")
+                                       secReq.With(*securityEnv)
+                                       continue
+                               }
+                               plugins[key] = value
+                       }
+                       path.Security = secReq
+                       extensions["x-apisix-plugins"] = plugins
+               }
+
+               extensions["x-apisix-priority"] = route.Priority
+               extensions["x-apisix-status"] = route.Status
+               extensions["x-apisix-enableWebsocket"] = route.EnableWebsocket
+               path.Extensions = extensions
+               path.Parameters = paramsRefs
+               path.RequestBody = &openapi3.RequestBodyRef{Value: requestBody}
+               path.Responses = openapi3.NewResponses()
+               for i := range route.Methods {
+                       switch strings.ToUpper(route.Methods[i]) {
+                       case "GET":
+                               //pathItem.Get = path
+                               pathItem.Get = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Get",
+                                       Parameters:     path.Parameters,
+                                       RequestBody:    path.RequestBody,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+
+                       case "POST":
+                               //pathItem.Post = path
+                               pathItem.Post = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Post",
+                                       Parameters:     path.Parameters,
+                                       RequestBody:    path.RequestBody,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+                       case "PUT":
+                               //pathItem.Put = path
+                               pathItem.Put = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Put",
+                                       Parameters:     path.Parameters,
+                                       RequestBody:    path.RequestBody,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+                       case "DELETE":
+                               //pathItem.Delete = path
+                               pathItem.Delete = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Delete",
+                                       Parameters:     path.Parameters,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+                       case "PATCH":
+                               //pathItem.Patch = path
+                               pathItem.Patch = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Patch",
+                                       Parameters:     path.Parameters,
+                                       RequestBody:    path.RequestBody,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+                       case "HEAD":
+                               //pathItem.Head = path
+                               pathItem.Head = &openapi3.Operation{
+                                       ExtensionProps: path.ExtensionProps,
+                                       Tags:           path.Tags,
+                                       Summary:        path.Summary,
+                                       Description:    path.Description,
+                                       OperationID:    path.OperationID + 
"Head",
+                                       Parameters:     path.Parameters,
+                                       RequestBody:    path.RequestBody,
+                                       Responses:      path.Responses,
+                                       Callbacks:      path.Callbacks,
+                                       Deprecated:     path.Deprecated,
+                                       Security:       path.Security,
+                                       Servers:        path.Servers,
+                                       ExternalDocs:   path.ExternalDocs,
+                               }
+                       }
+               }
+       }
+       components.SecuritySchemes = secSchemas
+       swagger := openapi3.Swagger{
+               OpenAPI:    openApi,
+               Info:       &openapi3.Info{Title: title, Version: openApi},
+               Paths:      paths,
+               Components: *components,
+       }
+       return &swagger
+}

Review comment:
       It's really long at the moment. I'll try to take it apart

##########
File path: api/test/e2e/route_export_test.go
##########
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+
+import (
+       "net/http"
+       "testing"
+)
+
+func TestRoute_Export(t *testing.T) {
+       tests := []HttpTestCase{
+               {
+                       Desc:         "hit route that not exist",
+                       Object:       APISIXExpect(t),
+                       Method:       http.MethodGet,
+                       Path:         "/hello_",
+                       Headers:      map[string]string{"Host": "foo.com"},
+                       ExpectStatus: http.StatusNotFound,
+                       ExpectBody:   "{\"error_msg\":\"404 Route Not 
Found\"}\n",
+               },
+               {
+                       Desc:   "create route",
+                       Object: ManagerApiExpect(t),
+                       Method: http.MethodPut,
+                       Path:   "/apisix/admin/routes/r1",
+                       Body: `{
+                               "name": "aaaa",
+                               "labels": {
+                                       "build":"16",
+                                       "env":"production",
+                                       "version":"v2"
+                               },
+                               "plugins": {
+                                       "limit-count": {
+                                               "count": 2,
+                                               "time_window": 60,
+                                               "rejected_code": 503,
+                                               "key": "remote_addr"
+                                       }
+                               },
+                               "status": 1,
+                               "uris": ["/hello_"],
+                               "hosts": ["foo.com", "*.bar.com"],
+                               "methods": ["GET", "POST"],
+                               "upstream": {
+                                       "nodes": {
+                                               "172.16.238.20:1980": 1
+                                       },
+                                       "type": "roundrobin"
+                               }
+                       }`,
+                       Headers:      map[string]string{"Authorization": token},
+                       ExpectStatus: http.StatusOK,
+               },
+               {
+                       Desc:         "export route",
+                       Object:       ManagerApiExpect(t),
+                       Method:       http.MethodPost,
+                       Path:         "/apisix/admin/routes/export/r1",
+                       Headers:      map[string]string{"Authorization": token},
+                       ExpectStatus: http.StatusOK,
+                       ExpectBody:   
"\"data\":{\"components\":{},\"info\":{\"title\":\"Routes 
Export\",\"version\":\"3.0.0\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello_\":{\"get\":{\"operationId\":\"aaaaGet\",\"requestBody\":{},\"responses\":{\"default\":{\"description\":\"\"}},\"security\":[],\"x-apisix-enableWebsocket\":false,\"x-apisix-hosts\":[\"foo.com\",\"*.bar.com\"],\"x-apisix-labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"},\"x-apisix-plugins\":{\"limit-count\":{\"count\":2,\"key\":\"remote_addr\",\"rejected_code\":503,\"time_window\":60}},\"x-apisix-priority\":0,\"x-apisix-status\":1,\"x-apisix-upstream\":{\"nodes\":{\"172.16.238.20:1980\":1},\"type\":\"roundrobin\"}},\"post\":{\"operationId\":\"aaaaPost\",\"requestBody\":{},\"responses\":{\"default\":{\"description\":\"\"}},\"security\":[],\"x-apisix-enableWebsocket\":false,\"x-apisix-hosts\":[\"foo.com\",\"*.bar.com\"],\"x-apisix-labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"},\"x-apisix-plugins\"
 
:{\"limit-count\":{\"count\":2,\"key\":\"remote_addr\",\"rejected_code\":503,\"time_window\":60}},\"x-apisix-priority\":0,\"x-apisix-status\":1,\"x-apisix-upstream\":{\"nodes\":{\"172.16.238.20:1980\":1},\"type\":\"roundrobin\"}}}}}",
+               },
+       }
+       for _, tc := range tests {
+               testCaseCheck(tc, t)
+       }
+
+       tests2 := []HttpTestCase{
+               {
+                       Desc:         "hit route2 that not exist",
+                       Object:       APISIXExpect(t),
+                       Method:       http.MethodGet,
+                       Path:         "/hello2",
+                       Headers:      map[string]string{"Host": "foo.com"},
+                       ExpectStatus: http.StatusNotFound,
+                       ExpectBody:   "{\"error_msg\":\"404 Route Not 
Found\"}\n",
+               },
+               {
+                       Desc:   "create route2",
+                       Object: ManagerApiExpect(t),
+                       Method: http.MethodPut,
+                       Path:   "/apisix/admin/routes/r2",
+                       Body: `{
+                               "name": "aaaa2",
+                               "labels": {
+                                       "build":"16",
+                                       "env":"production",
+                                       "version":"v2"
+                               },
+                               "plugins": {
+                                       "limit-count": {
+                                               "count": 2,
+                                               "time_window": 60,
+                                               "rejected_code": 503,
+                                               "key": "remote_addr"
+                                       }
+                               },
+                               "status": 1,
+                               "uri": "/hello2",
+                               "hosts": ["foo.com", "*.bar.com"],
+                               "methods": ["GET", "POST"],
+                               "upstream": {
+                                       "nodes": {
+                                               "172.16.238.20:1980": 1
+                                       },
+                                       "type": "roundrobin"
+                               }
+                       }`,
+                       Headers:      map[string]string{"Authorization": token},
+                       ExpectStatus: http.StatusOK,
+               },
+               {
+                       Desc:         "export route2",
+                       Object:       ManagerApiExpect(t),
+                       Method:       http.MethodPost,
+                       Path:         "/apisix/admin/routes/export/r2",
+                       Headers:      map[string]string{"Authorization": token},
+                       ExpectStatus: http.StatusOK,
+                       ExpectBody:   
"\"data\":{\"components\":{},\"info\":{\"title\":\"Routes 
Export\",\"version\":\"3.0.0\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello2\":{\"get\":{\"operationId\":\"aaaa2Get\",\"requestBody\":{},\"responses\":{\"default\":{\"description\":\"\"}},\"security\":[],\"x-apisix-enableWebsocket\":false,\"x-apisix-hosts\":[\"foo.com\",\"*.bar.com\"],\"x-apisix-labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"},\"x-apisix-plugins\":{\"limit-count\":{\"count\":2,\"key\":\"remote_addr\",\"rejected_code\":503,\"time_window\":60}},\"x-apisix-priority\":0,\"x-apisix-status\":1,\"x-apisix-upstream\":{\"nodes\":{\"172.16.238.20:1980\":1},\"type\":\"roundrobin\"}},\"post\":{\"operationId\":\"aaaa2Post\",\"requestBody\":{},\"responses\":{\"default\":{\"description\":\"\"}},\"security\":[],\"x-apisix-enableWebsocket\":false,\"x-apisix-hosts\":[\"foo.com\",\"*.bar.com\"],\"x-apisix-labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"},\"x-apisix-plugins
 
\":{\"limit-count\":{\"count\":2,\"key\":\"remote_addr\",\"rejected_code\":503,\"time_window\":60}},\"x-apisix-priority\":0,\"x-apisix-status\":1,\"x-apisix-upstream\":{\"nodes\":{\"172.16.238.20:1980\":1},\"type\":\"roundrobin\"}}}}}",
+               },
+       }
+       for _, tc := range tests2 {
+               testCaseCheck(tc, t)
+       }
+
+       tests3 := []HttpTestCase{
+

Review comment:
       done.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to