Implementing OpenApi 3 specs (converted from Swagger 2) (#53243)

* Adding OpenApi target to makefile, to covert swagger spec into OpenAPI 3
* Adding endpoint to server swaggerui with new openapi3 specs
* Passing output file as parameter for OpenApi3 specs
* Implement workaround for missing host and prefix domain
This commit is contained in:
lean.dev authored and GitHub committed 2022-08-04 13:51:12 -03:00
1 parent 138f03aad1
commit e8e7b59bb0
7 files changed
+145 -2

No files matched your search

+1
View File
@@ -29,3 +29,4 @@ theme.dark.generated.json
# Generated Swagger API specs
public/api-spec.json
public/api-merged.json
public/openapi3.json
+7 -2
View File
@@ -54,10 +54,15 @@ validate-api-spec: $(MERGED_SPEC_TARGET) $(SWAGGER) ## Validate API spec
$(SWAGGER) validate $(<)
clean-api-spec:
rm $(SPEC_TARGET) $(MERGED_SPEC_TARGET)
rm $(SPEC_TARGET) $(MERGED_SPEC_TARGET) $(OAPI_SPEC_TARGET)
##@ OpenAPI 3
OAPI_SPEC_TARGET = public/openapi3.json
openapi3-gen: swagger-api-spec ## Generates OpenApi 3 specs from the Swagger 2 already generated
$(GO) run scripts/openapi3/openapi3conv.go $(MERGED_SPEC_TARGET) $(OAPI_SPEC_TARGET)
##@ Building
gen-cue: ## Do all CUE/Thema code generation
@echo "generate code from .cue files"
go generate ./pkg/framework/coremodel
+1
View File
@@ -187,6 +187,7 @@ func (hs *HTTPServer) registerRoutes() {
if hs.Features.IsEnabled(featuremgmt.FlagSwaggerUi) {
r.Get("/swagger-ui", swaggerUI)
r.Get("/openapi3", openapi3)
}
// authed api
+11
View File
@@ -0,0 +1,11 @@
package api
import (
"net/http"
"github.com/grafana/grafana/pkg/models"
)
func openapi3(c *models.ReqContext) {
c.HTML(http.StatusOK, "openapi3", nil)
}
+1
View File
@@ -0,0 +1 @@
{"components":{"responses":{"SMTPNotEnabledError":{"description":"(empty)"},"acceptedResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseBody"}}},"description":"AcceptedResponse"},"addPermissionResponse":{"content":{"application/json":{"schema":{"properties":{"message":{"type":"string"},"permissionId":{"format":"int64","type":"integer"}},"type":"object"}}},"description":"(empty)"},"adminCreateUserResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserIdDTO"}}},"description":"(empty)"},"adminGetSettingsResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsBag"}}},"description":"(empty)"},"adminGetStatsResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStats"}}},"description":"(empty)"},"adminGetUserAuthTokensResponse":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserToken"},"type":"array"}}},"description":"(empty)"},"badRequestError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseBody"}}},"description":"BadRequestError is returned when the request is invalid and it cannot be processed."},"calculateDashboardDiffResponse":{"content":{"application/json":{"schema":{"items":{"format":"uint8","type":"integer"},"type":"array"}}},"description":"(empty)"},"conflictError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseBody"}}},"description":"ConflictError"},"contentResponse":{"content":{"application/json":{"schema":{"items":{"format":"uint8","type":"integer"},"type":"array"}}},"description":"(empty)"},"createCorrelationResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCorrelationResponseBody"}}},"description":"(empty)"},"createDashboardSnapshotResponse":{"content":{"application/json":{"schema":{"properties":{"deleteKey":{"description":"Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot.","type":"string"},"deleteUrl":{"type":"string"},"id":{"description":"Snapshot id","format":"int64","type":"integer"},"key":{"description":"Unique key","type":"string"},"url":{"type":"string"}},"type":"object"}}},"description":"(empty)"},"createOrUpdateDatasourceResponse":{"content":{"application/json":{"schema":{"properties":{"datasource":{"$ref":"#/components/schemas/DataSource"},"id":{"description":"ID Identifier of the new data source.","example":65,"format":"int64","type":"integer"},"message":{"description":"Message Message of the deleted dashboard.","example":"Data source added","type":"string"},"name":{"description":"Name of the new data source.","example":"My Data source","type":"string"}},"required":["id","name","message","datasource"],"type":"object"}}},"description":"(empty)"},"createOrgResponse":{"content":{"application/json":{"schema":{"properties":{"message":{"description":"Message Message of the created org.","example":"Data source added","type":"string"},"orgId":{"description":"ID Identifier of the created org.","example":65,"format":"int64","type":"integer"}},"required":["orgId","message"],"type":"object"}}},"description":"(empty)"},"createPlaylistResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Playlist"}}},"description":"(empty)"},"createReportResponse":{"content":{"application/json":{"schema":{"properties":{"id":{"format":"int64","type":"integer"},"message":{"type":"string"}},"type":"object"}}},"description":"(empty)"},"createRoleResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleDTO"}}},"description":"(empty)"},"createServiceAccountResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceAccountDTO"}}},"description":"(empty)"},"createTeamResponse":{"content":{"application/json":{"schema":{"properties":{"message":{"type":"string"},"teamId":{"format":"int64","type":"integer"}},"type":"object"}}},"description":"(empty)"},"createTokenResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewApiKeyResult"}}},"description":"(empty)"},"dashboardResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardFullWithMeta"}}},"description":"(empty)"},"dashboardVersionResponse":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardVersionMeta"}}},"description":"(empty)"},"dashboardVersionsResponse":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/DashboardVersionDTO"},"type":"array"}}},"description":"(empty)"},"deleteAlertNotificationChannelResponse":{"content":{"application/json":{"schema":{"properties":{"id":{"description":"ID Identifier of the deleted notification channel.","example":65,"format":"int64","type":"integer"},"message":{"description":"Message Message of the deleted notificatiton channel.","type":"string"}},"required":["id","message"],"type":"object"}}},"descriLine truncated
+63
View File
@@ -0,0 +1,63 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.3.0/swagger-ui.css" integrity="sha384-pzdBB6iZwPIzBHgXle+9cgvKuMgtWNrBopXkjrWnKCi3m4uJsPPdLQ4IPMqRDirS" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@4.3.0/swagger-ui-bundle.js" charset="UTF-8" integrity="sha384-BGJ5JzR5LEl4ETmxXXlZtXtMWj3uQ9jj9/OHe3yrn5rrtAyLOz1SyyzwMfuwZgPc" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/swagger-ui-dist@4.3.0/swagger-ui-standalone-preset.js" charset="UTF-8" integrity="sha384-AWSfISmlS8fS336GXRkpL0Uv6EbCpsFfXDUwmklhbb3SctGSuvXWBcbjERjgf/e4" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "/public/openapi3.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
filter: true,
tagsSorter: "alpha",
tryItOutEnabled: true
});
// End Swagger UI call region
window.ui = ui;
};
</script>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
package main
import (
"encoding/json"
"fmt"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi2conv"
"github.com/getkin/kin-openapi/openapi3"
"io/ioutil"
"os"
)
// main This simple script will take the swagger v2 spec generated by grafana and convert them into openapi 3
// saving them as new json file to be able lo load and show
// The first parameter, if present, will be the input file
// The second parameter, if present, will be the output file
func main() {
outFile := "public/openapi3.json"
inFile := "api-merged.json"
args := os.Args[1:]
// first parameter as the input
if len(args) > 0 && args[0] != "" {
inFile = args[0]
}
// second parameter as output
if len(args) > 1 && args[1] != "" {
outFile = args[1]
}
fmt.Printf("Reading swagger 2 file %s\n", inFile)
byt, err := ioutil.ReadFile(inFile)
if err != nil {
panic(err)
}
var doc2 openapi2.T
if err = json.Unmarshal(byt, &doc2); err != nil {
panic(err)
}
doc3, err := openapi2conv.ToV3(&doc2)
if err != nil {
panic(err)
}
// this is a workaround. In the swagger2 specs there ir no definition of the host, so the converter can not create
// a URL. Adding this will ensure that all the api calls start with "/api".
doc3.AddServer(&openapi3.Server{URL: "/api"})
j3, err := doc3.MarshalJSON()
if err != nil {
panic(err)
}
if err = ioutil.WriteFile(outFile, j3, 0644); err != nil {
panic(err)
}
fmt.Printf("OpenAPI specs generated in file %s\n", outFile)
}