mirror of https://github.com/linkerd/linkerd2.git
153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package profiles
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"path"
|
|
"regexp"
|
|
"sort"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"github.com/go-openapi/spec"
|
|
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha1"
|
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
var pathParamRegex = regexp.MustCompile(`\\{[^\}]*\\}`)
|
|
|
|
// RenderOpenAPI reads an OpenAPI spec file and renders the corresponding
|
|
// ServiceProfile to a buffer, given a namespace, service, and control plane
|
|
// namespace.
|
|
func RenderOpenAPI(fileName, namespace, name, controlPlaneNamespace string, w io.Writer) error {
|
|
|
|
input, err := readFile(fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bytes, err := ioutil.ReadAll(input)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading file: %s", err)
|
|
}
|
|
json, err := yaml.YAMLToJSON(bytes)
|
|
if err != nil {
|
|
return fmt.Errorf("Error parsing yaml: %s", err)
|
|
}
|
|
|
|
swagger := spec.Swagger{}
|
|
err = swagger.UnmarshalJSON(json)
|
|
if err != nil {
|
|
return fmt.Errorf("Error parsing OpenAPI spec: %s", err)
|
|
}
|
|
|
|
profile := swaggerToServiceProfile(swagger, namespace, name, controlPlaneNamespace)
|
|
|
|
return writeProfile(profile, w)
|
|
}
|
|
|
|
func swaggerToServiceProfile(swagger spec.Swagger, namespace, name, controlPlaneNamespace string) sp.ServiceProfile {
|
|
profile := sp.ServiceProfile{
|
|
ObjectMeta: meta_v1.ObjectMeta{
|
|
Name: fmt.Sprintf("%s.%s.svc.cluster.local", name, namespace),
|
|
Namespace: controlPlaneNamespace,
|
|
},
|
|
TypeMeta: ServiceProfileMeta,
|
|
}
|
|
|
|
routes := make([]*sp.RouteSpec, 0)
|
|
|
|
paths := make([]string, 0)
|
|
if swagger.Paths != nil {
|
|
for path := range swagger.Paths.Paths {
|
|
paths = append(paths, path)
|
|
}
|
|
sort.Strings(paths)
|
|
}
|
|
|
|
for _, relPath := range paths {
|
|
item := swagger.Paths.Paths[relPath]
|
|
path := path.Join(swagger.BasePath, relPath)
|
|
pathRegex := pathToRegex(path)
|
|
if item.Delete != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodDelete, item.Delete.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Get != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodGet, item.Get.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Head != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodHead, item.Head.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Options != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodOptions, item.Options.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Patch != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodPatch, item.Patch.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Post != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodPost, item.Post.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
if item.Put != nil {
|
|
spec := mkRouteSpec(path, pathRegex, http.MethodPut, item.Put.Responses)
|
|
routes = append(routes, spec)
|
|
}
|
|
}
|
|
|
|
profile.Spec.Routes = routes
|
|
return profile
|
|
}
|
|
|
|
func mkRouteSpec(path, pathRegex string, method string, responses *spec.Responses) *sp.RouteSpec {
|
|
return &sp.RouteSpec{
|
|
Name: fmt.Sprintf("%s %s", method, path),
|
|
Condition: toReqMatch(pathRegex, method),
|
|
ResponseClasses: toRspClasses(responses),
|
|
}
|
|
}
|
|
|
|
func pathToRegex(path string) string {
|
|
escaped := regexp.QuoteMeta(path)
|
|
return pathParamRegex.ReplaceAllLiteralString(escaped, "[^/]*")
|
|
}
|
|
|
|
func toReqMatch(path string, method string) *sp.RequestMatch {
|
|
return &sp.RequestMatch{
|
|
PathRegex: path,
|
|
Method: method,
|
|
}
|
|
}
|
|
|
|
func toRspClasses(responses *spec.Responses) []*sp.ResponseClass {
|
|
if responses == nil {
|
|
return nil
|
|
}
|
|
classes := make([]*sp.ResponseClass, 0)
|
|
|
|
statuses := make([]int, 0)
|
|
for status := range responses.StatusCodeResponses {
|
|
statuses = append(statuses, status)
|
|
}
|
|
sort.Ints(statuses)
|
|
|
|
for _, status := range statuses {
|
|
cond := &sp.ResponseMatch{
|
|
Status: &sp.Range{
|
|
Min: uint32(status),
|
|
Max: uint32(status),
|
|
},
|
|
}
|
|
classes = append(classes, &sp.ResponseClass{
|
|
Condition: cond,
|
|
IsFailure: status >= 500,
|
|
})
|
|
}
|
|
return classes
|
|
}
|