The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/api/docs/merge/merge_specs.go

147 lines
3.8 KiB

package main
import (
"bytes"
_ "embed"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
)
func mergeVectors(a, b []string) []string {
for _, p := range b {
exist := false
for _, op := range a {
if op == p {
exist = true
break
}
}
if !exist {
a = append(a, p)
}
}
return a
}
func compareDefinition(a, b spec.Schema) bool {
return reflect.DeepEqual(a.Type, b.Type) && a.Format == b.Format && reflect.DeepEqual(a.Properties, b.Properties)
}
// mergeSpecs merges OSS API spec with one or more other OpenAPI specs
func mergeSpecs(output string, sources ...string) error {
if len(sources) < 2 {
return fmt.Errorf("no APIs to merge")
}
f, err := os.Open(sources[0])
if err != nil {
return err
}
specData, err := ioutil.ReadAll(f)
if err != nil {
return err
}
var specOSS spec.Swagger
if err := json.Unmarshal(specData, &specOSS); err != nil {
return fmt.Errorf("failed to unmarshal original spec: %w", err)
}
for _, s := range sources[1:] {
additionalSpec, err := loads.JSONSpec(s)
if err != nil {
return fmt.Errorf("failed to load spec from: %s: %w", s, err)
}
// Merge consumes
specOSS.SwaggerProps.Consumes = mergeVectors(specOSS.SwaggerProps.Consumes, additionalSpec.OrigSpec().Consumes)
// Merge produces
specOSS.SwaggerProps.Produces = mergeVectors(specOSS.SwaggerProps.Produces, additionalSpec.OrigSpec().Produces)
// Merge schemes
specOSS.SwaggerProps.Schemes = mergeVectors(specOSS.SwaggerProps.Schemes, additionalSpec.OrigSpec().Schemes)
//TODO: When there are conflict between definitions, we need to error out, but here we need to fix the existing conflict first
// there are false positives, we will have to fix those by regenerate alerting api spec
for k, ad := range additionalSpec.OrigSpec().SwaggerProps.Definitions {
if ossd, exists := specOSS.SwaggerProps.Definitions[k]; exists {
if !compareDefinition(ad, ossd) {
fmt.Printf("the definition of %s differs in specs!\n", k)
}
}
specOSS.SwaggerProps.Definitions[k] = ad
}
for k, ar := range additionalSpec.OrigSpec().SwaggerProps.Responses {
if ossr, exists := specOSS.SwaggerProps.Responses[k]; exists {
if !reflect.DeepEqual(ar, ossr) {
fmt.Printf("the definition of response %s differs in specs!\n", k)
}
}
specOSS.SwaggerProps.Responses[k] = ar
}
for k, p := range additionalSpec.OrigSpec().SwaggerProps.Parameters {
specOSS.SwaggerProps.Parameters[k] = p
}
paths := additionalSpec.OrigSpec().SwaggerProps.Paths
if paths != nil {
for k, pi := range paths.Paths {
kk := strings.TrimPrefix(k, specOSS.BasePath) // remove base path if exists
if specOSS.SwaggerProps.Paths == nil {
specOSS.SwaggerProps.Paths = &spec.Paths{
Paths: make(map[string]spec.PathItem),
}
}
specOSS.SwaggerProps.Paths.Paths[kk] = pi
}
}
specOSS.SwaggerProps.Tags = append(specOSS.SwaggerProps.Tags, additionalSpec.OrigSpec().SwaggerProps.Tags...)
}
// write result to file
newSpec, err := specOSS.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to marshal result spec: %w", err)
}
var prettyJSON bytes.Buffer
err = json.Indent(&prettyJSON, newSpec, "", "\t")
if err != nil {
return fmt.Errorf("failed to intend new spec: %w", err)
}
f, err = os.Create(output)
if err != nil {
return fmt.Errorf("failed to create file for new spec: %w", err)
}
_, err = f.Write(prettyJSON.Bytes())
if err != nil {
return fmt.Errorf("failed to write new spec: %w", err)
}
// validate result
return nil
}
func main() {
output := flag.String("o", "../../../swagger-ui/merged.json", "the output path")
flag.Parse()
err := mergeSpecs(*output, flag.Args()...)
if err != nil {
fmt.Printf("something went wrong: %s\n", err.Error())
}
}