Compare commits
139 Commits
sriram/SQL
...
ds-apiserv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
352ac8fc5b | ||
|
|
00917091d7 | ||
|
|
d73e4a229f | ||
|
|
96e3fdbfd5 | ||
|
|
68a65af091 | ||
|
|
82c045e501 | ||
|
|
80d806a822 | ||
|
|
570146fb36 | ||
|
|
61ec394f59 | ||
|
|
ad0adf79bd | ||
|
|
1f396581a6 | ||
|
|
406502f351 | ||
|
|
9648c0956f | ||
|
|
008d373f7a | ||
|
|
3f1145fe3b | ||
|
|
09c5311797 | ||
|
|
6df663584c | ||
|
|
2614a917de | ||
|
|
a1015f7a9f | ||
|
|
f17765b70c | ||
|
|
9a05906299 | ||
|
|
969ae75b08 | ||
|
|
eeb6d105ed | ||
|
|
b93fb964b7 | ||
|
|
d1657d4684 | ||
|
|
63374d29c0 | ||
|
|
379aff5ff4 | ||
|
|
8bf8c07878 | ||
|
|
f7dc2f6e56 | ||
|
|
704f533846 | ||
|
|
82312c3418 | ||
|
|
87a9f26997 | ||
|
|
1fe4415682 | ||
|
|
a629b70c1f | ||
|
|
36fe8c6b61 | ||
|
|
e96dd1b12a | ||
|
|
a4adcf8896 | ||
|
|
1671a8644f | ||
|
|
bfcf649e8b | ||
|
|
ebb4cfadff | ||
|
|
29e9ae1918 | ||
|
|
540eb6c862 | ||
|
|
17b20fb464 | ||
|
|
dc986afd68 | ||
|
|
eba83d8973 | ||
|
|
cf89fb2a13 | ||
|
|
290e8a97f1 | ||
|
|
0ed24434c2 | ||
|
|
cbfb1e15ed | ||
|
|
47e379c8d9 | ||
|
|
500b029b25 | ||
|
|
bce28b8663 | ||
|
|
b0c9350580 | ||
|
|
76b4c687b0 | ||
|
|
0d6f718255 | ||
|
|
6b72ddb4d3 | ||
|
|
2f094fdcd9 | ||
|
|
6a0ce01d18 | ||
|
|
d00b8ab76d | ||
|
|
e577b8d0b8 | ||
|
|
2270d6cb22 | ||
|
|
c539be48d8 | ||
|
|
03a2153bd8 | ||
|
|
eb01a3e462 | ||
|
|
f6b6b62f5e | ||
|
|
5a2351387a | ||
|
|
78d9829d3b | ||
|
|
d9e1adaa48 | ||
|
|
949b521ac7 | ||
|
|
1d9572cdb2 | ||
|
|
f5e649183b | ||
|
|
4b44a83802 | ||
|
|
26dc101ecc | ||
|
|
ca2f2e8d9e | ||
|
|
0e10d9cd16 | ||
|
|
ed9789179b | ||
|
|
5890137232 | ||
|
|
471c4eb89d | ||
|
|
3ac63ea9c4 | ||
|
|
7d2d38fd94 | ||
|
|
80dda87868 | ||
|
|
04a4fd7c61 | ||
|
|
1d26b455fd | ||
|
|
8f0109a1ee | ||
|
|
798e9a32fc | ||
|
|
f4001d7cdc | ||
|
|
718c28438e | ||
|
|
b58b9144a5 | ||
|
|
96830de552 | ||
|
|
58c956eb19 | ||
|
|
b1d8e9ca41 | ||
|
|
4006a61964 | ||
|
|
0a24935e45 | ||
|
|
f25c9b0e03 | ||
|
|
5c478e98c4 | ||
|
|
ddc99a1dca | ||
|
|
e12d1ba6ca | ||
|
|
420e070aea | ||
|
|
732d4351de | ||
|
|
f842bb7af7 | ||
|
|
a1e2ba6617 | ||
|
|
1cade082fc | ||
|
|
ea38c4ad5a | ||
|
|
01926ab3c8 | ||
|
|
84a5282ea2 | ||
|
|
35d7bb880a | ||
|
|
251b7b4b4e | ||
|
|
4e3197a58f | ||
|
|
1a883d1167 | ||
|
|
cc2e96a558 | ||
|
|
6b2ebb2d65 | ||
|
|
7cc36672bb | ||
|
|
5be096833a | ||
|
|
076d1a5ad5 | ||
|
|
775ed81b58 | ||
|
|
fb0aaa321e | ||
|
|
c1c1f3a85c | ||
|
|
c0f1a6423c | ||
|
|
a44fdee0ef | ||
|
|
97a089ef05 | ||
|
|
b2799f977f | ||
|
|
2d4fd99e7a | ||
|
|
c4842845e7 | ||
|
|
e9105e6303 | ||
|
|
841e0bd5e5 | ||
|
|
5c80bc12f0 | ||
|
|
60981e813b | ||
|
|
94eea040e2 | ||
|
|
6869160e97 | ||
|
|
4dc6ad9257 | ||
|
|
b2c368e1cd | ||
|
|
1ea3b5440c | ||
|
|
9bbe311bdf | ||
|
|
5a338adf47 | ||
|
|
1d218d01b2 | ||
|
|
ae6ba4ba44 | ||
|
|
90614fcb91 | ||
|
|
1bedd399d6 | ||
|
|
0bff610506 |
44
pkg/apis/datasource/v0alpha1/openapi.go
Normal file
44
pkg/apis/datasource/v0alpha1/openapi.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package v0alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Optional extensions for an explicit datasource type
|
||||||
|
// NOTE: the properties from this structure will be populated by reading an app-sdk manifest.json
|
||||||
|
type DataSourceOpenAPIExtension struct {
|
||||||
|
// When specified, this will replace the default spec
|
||||||
|
DataSourceSpec *spec.Schema `json:"spec,omitempty"`
|
||||||
|
|
||||||
|
// Define which secure values are required
|
||||||
|
SecureValues []SecureValueInfo `json:"secureValues"`
|
||||||
|
|
||||||
|
// Examples added to the POST command
|
||||||
|
Examples map[string]*spec3.Example `json:"examples,omitempty"`
|
||||||
|
|
||||||
|
// Additional Schemas added to the response
|
||||||
|
Schemas map[string]*spec.Schema `json:"schemas,omitempty"`
|
||||||
|
|
||||||
|
// TODO: define query types dynamically here (currently hardcoded)
|
||||||
|
// Queries *queryV0.QueryTypeDefinitionList `json:"queries,omitempty"`
|
||||||
|
|
||||||
|
// Resource routes -- the paths exposed under:
|
||||||
|
// {group}/{version}/namespaces/{ns}/datasource/{name}/resource/{route}
|
||||||
|
Routes map[string]*spec3.Path `json:"routes,omitempty"`
|
||||||
|
|
||||||
|
// Proxy routes -- the paths exposed under:
|
||||||
|
// {group}/{version}/namespaces/{ns}/datasource/{name}/proxy/{proxy}
|
||||||
|
Proxy map[string]*spec3.Path `json:"proxy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecureValueInfo struct {
|
||||||
|
// The key
|
||||||
|
Key string `json:"string"`
|
||||||
|
|
||||||
|
// Description
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// Required secure values
|
||||||
|
Required bool `json:"required,omitempty"`
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// returns the current implementation version
|
// returns the current implementation version
|
||||||
@@ -117,6 +118,23 @@ func (j *Json) Interface() any {
|
|||||||
return j.data
|
return j.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the underlying data is empty
|
||||||
|
func (j *Json) IsEmpty() bool {
|
||||||
|
if j.data == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(j.data)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
|
||||||
|
if v.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Encode returns its marshaled data as `[]byte`
|
// Encode returns its marshaled data as `[]byte`
|
||||||
func (j *Json) Encode() ([]byte, error) {
|
func (j *Json) Encode() ([]byte, error) {
|
||||||
return j.MarshalJSON()
|
return j.MarshalJSON()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimplejson(t *testing.T) {
|
func TestSimplejson(t *testing.T) {
|
||||||
@@ -272,3 +273,45 @@ func TestMustJson(t *testing.T) {
|
|||||||
MustJson([]byte(`{`))
|
MustJson([]byte(`{`))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
empty bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty map (any)",
|
||||||
|
input: map[string]any{},
|
||||||
|
empty: true,
|
||||||
|
}, {
|
||||||
|
name: "empty map (string)",
|
||||||
|
input: map[string]string{},
|
||||||
|
empty: true,
|
||||||
|
}, {
|
||||||
|
name: "empty array (any)",
|
||||||
|
input: []any{},
|
||||||
|
empty: true,
|
||||||
|
}, {
|
||||||
|
name: "empty array (string)",
|
||||||
|
input: []string{},
|
||||||
|
empty: true,
|
||||||
|
}, {
|
||||||
|
name: "empty string",
|
||||||
|
input: "",
|
||||||
|
empty: true,
|
||||||
|
}, {
|
||||||
|
name: "non empty string",
|
||||||
|
input: "hello",
|
||||||
|
}, {
|
||||||
|
name: "key value",
|
||||||
|
input: map[string]any{"key": "value"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
js := NewFromAny(tc.input)
|
||||||
|
require.Equal(t, tc.empty, js.IsEmpty())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,19 +55,33 @@ func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.Data
|
|||||||
SetBasicAuthUser(ds.BasicAuthUser).
|
SetBasicAuthUser(ds.BasicAuthUser).
|
||||||
SetWithCredentials(ds.WithCredentials).
|
SetWithCredentials(ds.WithCredentials).
|
||||||
SetIsDefault(ds.IsDefault).
|
SetIsDefault(ds.IsDefault).
|
||||||
SetReadOnly(ds.ReadOnly).
|
SetReadOnly(ds.ReadOnly)
|
||||||
SetJSONData(ds.JsonData)
|
|
||||||
|
|
||||||
|
if ds.JsonData != nil && !ds.JsonData.IsEmpty() {
|
||||||
|
obj.Spec.SetJSONData(ds.JsonData.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := int64(0)
|
||||||
if !ds.Created.IsZero() {
|
if !ds.Created.IsZero() {
|
||||||
obj.CreationTimestamp = metav1.NewTime(ds.Created)
|
obj.CreationTimestamp = metav1.NewTime(ds.Created)
|
||||||
|
rv = ds.Created.UnixMilli()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only mark updated if the times have actually changed
|
||||||
if !ds.Updated.IsZero() {
|
if !ds.Updated.IsZero() {
|
||||||
obj.ResourceVersion = fmt.Sprintf("%d", ds.Updated.UnixMilli())
|
rv = ds.Updated.UnixMilli()
|
||||||
obj.Annotations = map[string]string{
|
delta := rv - obj.CreationTimestamp.UnixMilli()
|
||||||
utils.AnnoKeyUpdatedTimestamp: ds.Updated.Format(time.RFC3339),
|
if delta > 1500 {
|
||||||
|
obj.Annotations = map[string]string{
|
||||||
|
utils.AnnoKeyUpdatedTimestamp: ds.Updated.UTC().Format(time.RFC3339),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rv > 0 {
|
||||||
|
obj.ResourceVersion = strconv.FormatInt(rv, 10)
|
||||||
|
}
|
||||||
|
|
||||||
if ds.APIVersion != "" {
|
if ds.APIVersion != "" {
|
||||||
obj.APIVersion = fmt.Sprintf("%s/%s", r.group, ds.APIVersion)
|
obj.APIVersion = fmt.Sprintf("%s/%s", r.group, ds.APIVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
275
pkg/registry/apis/datasource/hardcoded/testdata.go
Normal file
275
pkg/registry/apis/datasource/hardcoded/testdata.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
package hardcoded
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
|
||||||
|
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestdataOpenAPIExtension() (*datasourceV0.DataSourceOpenAPIExtension, error) {
|
||||||
|
oas := &datasourceV0.DataSourceOpenAPIExtension{
|
||||||
|
SecureValues: []datasourceV0.SecureValueInfo{ // empty
|
||||||
|
// {
|
||||||
|
// Key: "aaa",
|
||||||
|
// Description: "describe aaa",
|
||||||
|
// Required: true,
|
||||||
|
// }, {
|
||||||
|
// Key: "bbb",
|
||||||
|
// Description: "describe bbb",
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
|
||||||
|
Examples: map[string]*spec3.Example{
|
||||||
|
"": {
|
||||||
|
ExampleProps: spec3.ExampleProps{
|
||||||
|
Summary: "Empty testdata",
|
||||||
|
Value: map[string]any{
|
||||||
|
"apiVersion": "testdata.datasource.grafana.app/v0alpha1",
|
||||||
|
"kind": "DataSource",
|
||||||
|
"metadata": map[string]any{
|
||||||
|
"name": "my-testdata-datasource",
|
||||||
|
},
|
||||||
|
"spec": map[string]any{
|
||||||
|
"title": "My TestData Datasource",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"with-url": {
|
||||||
|
ExampleProps: spec3.ExampleProps{
|
||||||
|
Summary: "Testdata with URL (not used)",
|
||||||
|
Value: map[string]any{
|
||||||
|
"apiVersion": "testdata.datasource.grafana.app/v0alpha1",
|
||||||
|
"kind": "DataSource",
|
||||||
|
"metadata": map[string]any{
|
||||||
|
"name": "testdata-with-url",
|
||||||
|
},
|
||||||
|
"spec": map[string]any{
|
||||||
|
"title": "TestData with URL",
|
||||||
|
"url": "http://example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy spec
|
||||||
|
p := &spec.Schema{} //SchemaProps: spec.SchemaProps{Type: []string{"object"}}}
|
||||||
|
p.Description = "Test data does not require any explicit configuration"
|
||||||
|
p.Required = []string{"title"}
|
||||||
|
p.AdditionalProperties = &spec.SchemaOrBool{Allows: false}
|
||||||
|
p.Properties = map[string]spec.Schema{
|
||||||
|
"title": *spec.StringProperty().WithDescription("display name"),
|
||||||
|
"url": *spec.StringProperty().WithDescription("not used"),
|
||||||
|
}
|
||||||
|
p.Example = map[string]any{
|
||||||
|
"url": "http://xxxx",
|
||||||
|
}
|
||||||
|
oas.DataSourceSpec = p
|
||||||
|
|
||||||
|
// Resource routes
|
||||||
|
// https://github.com/grafana/grafana/blob/main/pkg/tsdb/grafana-testdata-datasource/resource_handler.go#L20
|
||||||
|
unstructured := spec.MapProperty(nil)
|
||||||
|
unstructuredResponse := &spec3.Responses{
|
||||||
|
ResponsesProps: spec3.ResponsesProps{
|
||||||
|
Default: &spec3.Response{
|
||||||
|
ResponseProps: spec3.ResponseProps{
|
||||||
|
Content: map[string]*spec3.MediaType{
|
||||||
|
"application/json": {
|
||||||
|
MediaTypeProps: spec3.MediaTypeProps{
|
||||||
|
Schema: unstructured,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unstructuredRequest := &spec3.RequestBody{
|
||||||
|
RequestBodyProps: spec3.RequestBodyProps{
|
||||||
|
Content: map[string]*spec3.MediaType{
|
||||||
|
"application/json": {
|
||||||
|
MediaTypeProps: spec3.MediaTypeProps{
|
||||||
|
Schema: unstructured,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
oas.Routes = map[string]*spec3.Path{
|
||||||
|
"": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "hello world",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Responses: &spec3.Responses{
|
||||||
|
ResponsesProps: spec3.ResponsesProps{
|
||||||
|
Default: &spec3.Response{
|
||||||
|
ResponseProps: spec3.ResponseProps{
|
||||||
|
Content: map[string]*spec3.MediaType{
|
||||||
|
"text/plain": {
|
||||||
|
MediaTypeProps: spec3.MediaTypeProps{
|
||||||
|
Schema: spec.StringProperty(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/scenarios": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "hello world",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/stream": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "Get streaming response",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Parameters: []*spec3.Parameter{
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "count",
|
||||||
|
In: "query",
|
||||||
|
Schema: spec.Int64Property(),
|
||||||
|
Description: "number of points that will be returned",
|
||||||
|
Example: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "start",
|
||||||
|
In: "query",
|
||||||
|
Schema: spec.Int64Property(),
|
||||||
|
Description: "the start value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "flush",
|
||||||
|
In: "query",
|
||||||
|
Schema: spec.Int64Property(),
|
||||||
|
Description: "How often the result is flushed (1-100%)",
|
||||||
|
Example: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "speed",
|
||||||
|
In: "query",
|
||||||
|
Schema: spec.StringProperty(),
|
||||||
|
Description: "the clock cycle",
|
||||||
|
Example: "100ms",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "format",
|
||||||
|
In: "query",
|
||||||
|
Schema: spec.StringProperty().WithEnum("json", "influx"),
|
||||||
|
Description: "the response format",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/boom": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "force a panic",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Post: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/test": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "Echo any request",
|
||||||
|
Post: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
RequestBody: unstructuredRequest,
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/test/json": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Summary: "Echo json request",
|
||||||
|
Post: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
RequestBody: unstructuredRequest,
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/sims": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Description: "Get list of simulations",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/sim/{key}": {
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Description: "Get list of simulations",
|
||||||
|
Get: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Parameters: []*spec3.Parameter{
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "key",
|
||||||
|
In: "path",
|
||||||
|
Description: "simulation key (should include hz)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Post: &spec3.Operation{
|
||||||
|
OperationProps: spec3.OperationProps{
|
||||||
|
Parameters: []*spec3.Parameter{
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "key",
|
||||||
|
In: "path",
|
||||||
|
Description: "simulation key (should include hz)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RequestBody: unstructuredRequest,
|
||||||
|
Responses: unstructuredResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return oas, nil
|
||||||
|
}
|
||||||
24
pkg/registry/apis/datasource/hardcoded/testdata_test.go
Normal file
24
pkg/registry/apis/datasource/hardcoded/testdata_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package hardcoded
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
// "sigs.k8s.io/yaml" // uses the same structure as json!
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpec(t *testing.T) {
|
||||||
|
info, err := TestdataOpenAPIExtension()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, info)
|
||||||
|
|
||||||
|
jj, err := json.MarshalIndent(info, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Printf("%s\n", string(jj))
|
||||||
|
|
||||||
|
// jj, err = yaml.Marshal(info)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
// fmt.Printf("%s\n", string(jj))
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package datasource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,11 +10,15 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
|
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -84,7 +89,16 @@ func (s *legacyStorage) Create(ctx context.Context, obj runtime.Object, createVa
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("expected a datasource object")
|
return nil, fmt.Errorf("expected a datasource object")
|
||||||
}
|
}
|
||||||
return s.datasources.CreateDataSource(ctx, ds)
|
obj, err := s.datasources.CreateDataSource(ctx, ds)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, datasources.ErrDataSourceNameExists):
|
||||||
|
return nil, apierrors.NewInvalid(s.resourceInfo.GroupVersionKind().GroupKind(), ds.Name, field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("spec", "title"), ds.Spec.Title(), "a datasource with this title already exists")})
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update implements rest.Updater.
|
// Update implements rest.Updater.
|
||||||
|
|||||||
@@ -2,17 +2,35 @@ package datasource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kube-openapi/pkg/spec3"
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
|
||||||
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
|
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
|
||||||
|
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||||
// The plugin description
|
// The plugin description
|
||||||
oas.Info.Description = b.pluginJSON.Info.Description
|
oas.Info.Description = b.pluginJSON.Info.Description
|
||||||
|
|
||||||
|
// Add plugin information
|
||||||
|
info := map[string]any{
|
||||||
|
"plugin": b.pluginJSON.ID,
|
||||||
|
}
|
||||||
|
if b.pluginJSON.Info.Version != "" {
|
||||||
|
info["version"] = b.pluginJSON.Info.Version
|
||||||
|
}
|
||||||
|
if b.pluginJSON.Info.Build.Time > 0 {
|
||||||
|
info["build"] = b.pluginJSON.Info.Build.Time
|
||||||
|
}
|
||||||
|
oas.Info.AddExtension("plugin", info)
|
||||||
|
|
||||||
// The root api URL
|
// The root api URL
|
||||||
root := "/apis/" + b.datasourceResourceInfo.GroupVersion().String() + "/"
|
root := "/apis/" + b.datasourceResourceInfo.GroupVersion().String() + "/"
|
||||||
|
|
||||||
@@ -28,6 +46,17 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the operation ID for the query path
|
||||||
|
query := oas.Paths.Paths[root+"namespaces/{namespace}/datasources/{name}/query"]
|
||||||
|
if query != nil && query.Post != nil {
|
||||||
|
query.Post.OperationId = "queryDataSource"
|
||||||
|
for _, p := range query.Parameters {
|
||||||
|
if p.Name == "name" {
|
||||||
|
p.Description = "DataSource identifier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hide the resource routes -- explicit ones will be added if defined below
|
// Hide the resource routes -- explicit ones will be added if defined below
|
||||||
prefix := root + "namespaces/{namespace}/datasources/{name}/resource"
|
prefix := root + "namespaces/{namespace}/datasources/{name}/resource"
|
||||||
r := oas.Paths.Paths[prefix]
|
r := oas.Paths.Paths[prefix]
|
||||||
@@ -52,7 +81,7 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
|
|||||||
|
|
||||||
// Mark connections as deprecated
|
// Mark connections as deprecated
|
||||||
delete(oas.Paths.Paths, root+"namespaces/{namespace}/connections/{name}")
|
delete(oas.Paths.Paths, root+"namespaces/{namespace}/connections/{name}")
|
||||||
query := oas.Paths.Paths[root+"namespaces/{namespace}/connections/{name}/query"]
|
query = oas.Paths.Paths[root+"namespaces/{namespace}/connections/{name}/query"]
|
||||||
for query == nil || query.Post == nil {
|
for query == nil || query.Post == nil {
|
||||||
return nil, fmt.Errorf("missing temporary connection path")
|
return nil, fmt.Errorf("missing temporary connection path")
|
||||||
}
|
}
|
||||||
@@ -70,5 +99,111 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.schemaProvider == nil {
|
||||||
|
return oas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
custom, err := b.schemaProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return applyCustomSchemas(root, ds, oas, custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCustomSchemas(root string, ds *spec.Schema, oas *spec3.OpenAPI, custom *datasourceV0.DataSourceOpenAPIExtension) (*spec3.OpenAPI, error) {
|
||||||
|
if custom == nil {
|
||||||
|
return oas, nil // nothing special
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom schemas
|
||||||
|
maps.Copy(oas.Components.Schemas, custom.Schemas)
|
||||||
|
|
||||||
|
// Replace the generic DataSourceSpec with the explicit one
|
||||||
|
if custom.DataSourceSpec != nil {
|
||||||
|
oas.Components.Schemas["DataSourceSpec"] = custom.DataSourceSpec
|
||||||
|
ds.Properties["spec"] = spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Ref: spec.MustCreateRef("#/components/schemas/DataSourceSpec"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(custom.SecureValues) > 0 {
|
||||||
|
example := common.InlineSecureValues{}
|
||||||
|
ref := spec.MustCreateRef("#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue")
|
||||||
|
secure := &spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Properties: make(map[string]spec.Schema),
|
||||||
|
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
|
||||||
|
}}
|
||||||
|
secure.Description = "custom secure value definition"
|
||||||
|
|
||||||
|
for _, v := range custom.SecureValues {
|
||||||
|
secure.Properties[v.Key] = spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: v.Description,
|
||||||
|
Ref: ref,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if v.Required {
|
||||||
|
secure.Required = append(secure.Required, v.Key)
|
||||||
|
example[v.Key] = common.InlineSecureValue{Create: "***"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(example) > 0 {
|
||||||
|
secure.Example = example
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link the explicit secure values in the resource
|
||||||
|
oas.Components.Schemas["SecureValues"] = secure
|
||||||
|
ds.Properties["secure"] = spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Ref: spec.MustCreateRef("#/components/schemas/SecureValues"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add examples to the POST request
|
||||||
|
if len(custom.Examples) > 0 {
|
||||||
|
ds := oas.Paths.Paths[root+"namespaces/{namespace}/datasources"]
|
||||||
|
if ds != nil && ds.Post != nil {
|
||||||
|
for _, c := range ds.Post.RequestBody.Content {
|
||||||
|
c.Examples = custom.Examples
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(custom.Routes) > 0 {
|
||||||
|
ds := oas.Paths.Paths[root+"namespaces/{namespace}/datasources/{name}"]
|
||||||
|
if ds == nil || len(ds.Parameters) < 2 {
|
||||||
|
return nil, fmt.Errorf("missing Parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := root + "namespaces/{namespace}/datasources/{name}/resource"
|
||||||
|
for k := range oas.Paths.Paths {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
delete(oas.Paths.Paths, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range custom.Routes {
|
||||||
|
if k != "" && !strings.HasPrefix(k, "/") {
|
||||||
|
return nil, fmt.Errorf("path must have slash prefix")
|
||||||
|
}
|
||||||
|
v.Parameters = append(v.Parameters, ds.Parameters[0:2]...)
|
||||||
|
for m, op := range builder.GetPathOperations(v) {
|
||||||
|
if op.Extensions == nil {
|
||||||
|
op.Extensions = make(spec.Extensions)
|
||||||
|
}
|
||||||
|
if !slices.Contains(op.Tags, "Route") {
|
||||||
|
op.Tags = append(op.Tags, "Route") // Custom resource?
|
||||||
|
}
|
||||||
|
tmp := strings.ReplaceAll(strings.ReplaceAll(k, "{", ""), "}", "")
|
||||||
|
op.OperationId = fmt.Sprintf("%s_route%s", strings.ToLower(m), strings.ReplaceAll(tmp, "/", "_"))
|
||||||
|
}
|
||||||
|
oas.Paths.Paths[prefix+k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
return oas, nil
|
return oas, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||||
"github.com/grafana/grafana/pkg/promlib/models"
|
"github.com/grafana/grafana/pkg/promlib/models"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/datasource/hardcoded"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
|
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||||
@@ -39,14 +40,16 @@ var (
|
|||||||
// DataSourceAPIBuilder is used just so wire has something unique to return
|
// DataSourceAPIBuilder is used just so wire has something unique to return
|
||||||
type DataSourceAPIBuilder struct {
|
type DataSourceAPIBuilder struct {
|
||||||
datasourceResourceInfo utils.ResourceInfo
|
datasourceResourceInfo utils.ResourceInfo
|
||||||
pluginJSON plugins.JSONData
|
|
||||||
client PluginClient // will only ever be called with the same plugin id!
|
pluginJSON plugins.JSONData
|
||||||
datasources PluginDatasourceProvider
|
client PluginClient // will only ever be called with the same plugin id!
|
||||||
contextProvider PluginContextWrapper
|
datasources PluginDatasourceProvider
|
||||||
accessControl accesscontrol.AccessControl
|
contextProvider PluginContextWrapper
|
||||||
queryTypes *queryV0.QueryTypeDefinitionList
|
accessControl accesscontrol.AccessControl
|
||||||
configCrudUseNewApis bool
|
queryTypes *queryV0.QueryTypeDefinitionList
|
||||||
dataSourceCRUDMetric *prometheus.HistogramVec
|
schemaProvider func() (*datasourceV0.DataSourceOpenAPIExtension, error)
|
||||||
|
configCrudUseNewApis bool
|
||||||
|
dataSourceCRUDMetric *prometheus.HistogramVec
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAPIService(
|
func RegisterAPIService(
|
||||||
@@ -59,8 +62,17 @@ func RegisterAPIService(
|
|||||||
reg prometheus.Registerer,
|
reg prometheus.Registerer,
|
||||||
pluginSources sources.Registry,
|
pluginSources sources.Registry,
|
||||||
) (*DataSourceAPIBuilder, error) {
|
) (*DataSourceAPIBuilder, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
useQueryTypes := features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes)
|
||||||
|
|
||||||
|
//nolint:staticcheck
|
||||||
|
configCrudUseNewApis := features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections)
|
||||||
|
|
||||||
|
//nolint:staticcheck
|
||||||
|
isExperimental := features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)
|
||||||
|
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
if !features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections) && !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
if !configCrudUseNewApis && !isExperimental {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,28 +94,33 @@ func RegisterAPIService(
|
|||||||
return nil, fmt.Errorf("error getting list of datasource plugins: %s", err)
|
return nil, fmt.Errorf("error getting list of datasource plugins: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pluginJSON := range pluginJSONs {
|
// For the ST runner, the client can talk to any plugin
|
||||||
client, ok := pluginClient.(PluginClient)
|
client, ok := pluginClient.(PluginClient)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("plugin client is not a PluginClient: %T", pluginClient)
|
return nil, fmt.Errorf("plugin client is not a PluginClient: %T", pluginClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, pluginJSON := range pluginJSONs {
|
||||||
builder, err = NewDataSourceAPIBuilder(
|
builder, err = NewDataSourceAPIBuilder(
|
||||||
pluginJSON,
|
pluginJSON,
|
||||||
client,
|
client,
|
||||||
datasources.GetDatasourceProvider(pluginJSON),
|
datasources.GetDatasourceProvider(pluginJSON),
|
||||||
contextProvider,
|
contextProvider,
|
||||||
accessControl,
|
accessControl,
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
useQueryTypes, // Exposes the query type OpenAPI schema
|
||||||
features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes),
|
configCrudUseNewApis, // Enables the new connections-based datasource config CRUD APIs
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
|
||||||
features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
builder.SetDataSourceCRUDMetrics(dataSourceCRUDMetric)
|
builder.SetDataSourceCRUDMetrics(dataSourceCRUDMetric)
|
||||||
|
|
||||||
|
// Hardcoded schemas for testdata
|
||||||
|
// NOTE: this will be driven by the pluginJSON/manifest soon
|
||||||
|
if pluginJSON.ID == "grafana-testdata-datasource" {
|
||||||
|
builder.schemaProvider = hardcoded.TestdataOpenAPIExtension
|
||||||
|
}
|
||||||
|
|
||||||
apiRegistrar.RegisterAPI(builder)
|
apiRegistrar.RegisterAPI(builder)
|
||||||
}
|
}
|
||||||
return builder, nil // only used for wire
|
return builder, nil // only used for wire
|
||||||
@@ -247,6 +264,7 @@ func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Read-only access to datasource connection info
|
||||||
storage[ds.StoragePath()] = &connectionAccess{
|
storage[ds.StoragePath()] = &connectionAccess{
|
||||||
datasources: b.datasources,
|
datasources: b.datasources,
|
||||||
resourceInfo: ds,
|
resourceInfo: ds,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"name": "unique-identifier",
|
"name": "unique-identifier",
|
||||||
"namespace": "org-0",
|
"namespace": "org-0",
|
||||||
"uid": "YpaSG5GQAdxtLZtF6BqQWCeYXOhbVi5C4Cg4oILnJC0X",
|
"uid": "YpaSG5GQAdxtLZtF6BqQWCeYXOhbVi5C4Cg4oILnJC0X",
|
||||||
|
"resourceVersion": "1015203600000",
|
||||||
"generation": 8,
|
"generation": 8,
|
||||||
"creationTimestamp": "2002-03-04T01:00:00Z",
|
"creationTimestamp": "2002-03-04T01:00:00Z",
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -10,7 +11,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"jsonData": null,
|
|
||||||
"title": "Display name"
|
"title": "Display name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX"
|
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"jsonData": null,
|
|
||||||
"title": "Hello testdata"
|
"title": "Hello testdata"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"maps"
|
"maps"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -232,11 +233,9 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s
|
|||||||
parent := copy.Paths.Paths[path[:idx+6]]
|
parent := copy.Paths.Paths[path[:idx+6]]
|
||||||
if parent != nil && parent.Get != nil {
|
if parent != nil && parent.Get != nil {
|
||||||
for _, op := range GetPathOperations(spec) {
|
for _, op := range GetPathOperations(spec) {
|
||||||
if op != nil && op.Extensions != nil {
|
action, ok := op.Extensions.GetString("x-kubernetes-action")
|
||||||
action, ok := op.Extensions.GetString("x-kubernetes-action")
|
if ok && action == "connect" {
|
||||||
if ok && action == "connect" {
|
op.Tags = parent.Get.Tags
|
||||||
op.Tags = parent.Get.Tags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,15 +248,32 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPathOperations(path *spec3.Path) []*spec3.Operation {
|
// GetPathOperations returns the set of non-nil operations defined on a path
|
||||||
return []*spec3.Operation{
|
func GetPathOperations(path *spec3.Path) map[string]*spec3.Operation {
|
||||||
path.Get,
|
ops := make(map[string]*spec3.Operation)
|
||||||
path.Head,
|
if path.Get != nil {
|
||||||
path.Delete,
|
ops[http.MethodGet] = path.Get
|
||||||
path.Patch,
|
|
||||||
path.Post,
|
|
||||||
path.Put,
|
|
||||||
path.Trace,
|
|
||||||
path.Options,
|
|
||||||
}
|
}
|
||||||
|
if path.Head != nil {
|
||||||
|
ops[http.MethodHead] = path.Head
|
||||||
|
}
|
||||||
|
if path.Delete != nil {
|
||||||
|
ops[http.MethodDelete] = path.Delete
|
||||||
|
}
|
||||||
|
if path.Post != nil {
|
||||||
|
ops[http.MethodPost] = path.Post
|
||||||
|
}
|
||||||
|
if path.Put != nil {
|
||||||
|
ops[http.MethodPut] = path.Put
|
||||||
|
}
|
||||||
|
if path.Patch != nil {
|
||||||
|
ops[http.MethodPatch] = path.Patch
|
||||||
|
}
|
||||||
|
if path.Trace != nil {
|
||||||
|
ops[http.MethodTrace] = path.Trace
|
||||||
|
}
|
||||||
|
if path.Options != nil {
|
||||||
|
ops[http.MethodOptions] = path.Options
|
||||||
|
}
|
||||||
|
return ops
|
||||||
}
|
}
|
||||||
|
|||||||
76
pkg/services/apiserver/builder/openapi_test.go
Normal file
76
pkg/services/apiserver/builder/openapi_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOpenAPI_GetPathOperations(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input *spec3.Path
|
||||||
|
expect []string // the methods we should see
|
||||||
|
exclude []string // the methods we should never see
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "some operations",
|
||||||
|
input: &spec3.Path{
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Get: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "get"}},
|
||||||
|
Post: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "post"}},
|
||||||
|
Delete: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "delete"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []string{"GET", "POST", "DELETE"},
|
||||||
|
exclude: []string{"PUT", "PATCH", "OPTIONS", "HEAD", "TRACE"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all operations",
|
||||||
|
input: &spec3.Path{
|
||||||
|
PathProps: spec3.PathProps{
|
||||||
|
Get: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "get"}},
|
||||||
|
Post: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "post"}},
|
||||||
|
Delete: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "delete"}},
|
||||||
|
Put: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "put"}},
|
||||||
|
Patch: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "patch"}},
|
||||||
|
Options: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "options"}},
|
||||||
|
Head: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "head"}},
|
||||||
|
Trace: &spec3.Operation{OperationProps: spec3.OperationProps{Summary: "trace"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []string{"GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD", "TRACE"},
|
||||||
|
exclude: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
expect := make(map[string]bool)
|
||||||
|
for _, k := range tt.expect {
|
||||||
|
expect[k] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, op := range GetPathOperations(tt.input) {
|
||||||
|
require.NotNil(t, op)
|
||||||
|
require.Equal(t, strings.ToLower(k), op.Summary)
|
||||||
|
|
||||||
|
if !expect[k] {
|
||||||
|
if slices.Contains(tt.expect, k) {
|
||||||
|
require.Fail(t, "method returned multiple times", k)
|
||||||
|
} else {
|
||||||
|
require.Fail(t, "unexpected method", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(expect, k)
|
||||||
|
require.NotContains(t, tt.exclude, k, "exclude")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expect) > 0 {
|
||||||
|
require.Fail(t, "missing expected method", expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -873,10 +873,19 @@ func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h
|
|||||||
require.Failf(t, "Not OK", "Code[%d] %s", rsp.Response.StatusCode, string(rsp.Body))
|
require.Failf(t, "Not OK", "Code[%d] %s", rsp.Response.StatusCode, string(rsp.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var prettyJSON bytes.Buffer
|
schema := map[string]any{}
|
||||||
err := json.Indent(&prettyJSON, rsp.Body, "", " ")
|
err := json.Unmarshal(rsp.Body, &schema)
|
||||||
|
require.NoError(t, err)
|
||||||
|
info, found, err := unstructured.NestedMap(schema, "info", "plugin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
if found {
|
||||||
|
delete(info, "version") // the version is unstable in test environments
|
||||||
|
err = unstructured.SetNestedMap(schema, info, "info", "plugin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty, err := json.MarshalIndent(schema, "", " ")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
pretty := prettyJSON.String()
|
|
||||||
|
|
||||||
write := false
|
write := false
|
||||||
fpath := filepath.Join(dir, fmt.Sprintf("%s-%s.json", gv.Group, gv.Version))
|
fpath := filepath.Join(dir, fmt.Sprintf("%s-%s.json", gv.Group, gv.Version))
|
||||||
@@ -885,7 +894,7 @@ func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h
|
|||||||
// We can ignore the gosec G304 warning since this is a test and the function is only called with explicit paths
|
// We can ignore the gosec G304 warning since this is a test and the function is only called with explicit paths
|
||||||
body, err := os.ReadFile(fpath)
|
body, err := os.ReadFile(fpath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !assert.JSONEq(t, string(body), pretty) {
|
if !assert.JSONEq(t, string(body), string(pretty)) {
|
||||||
t.Logf("openapi spec has changed: %s", path)
|
t.Logf("openapi spec has changed: %s", path)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
write = true
|
write = true
|
||||||
@@ -896,7 +905,7 @@ func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
if write {
|
if write {
|
||||||
e2 := os.WriteFile(fpath, []byte(pretty), 0o644)
|
e2 := os.WriteFile(fpath, pretty, 0o644)
|
||||||
if e2 != nil {
|
if e2 != nil {
|
||||||
t.Errorf("error writing file: %s", e2.Error())
|
t.Errorf("error writing file: %s", e2.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"description": "Generates test data in different forms",
|
"description": "Generates test data in different forms",
|
||||||
"title": "testdata.datasource.grafana.app/v0alpha1"
|
"title": "testdata.datasource.grafana.app/v0alpha1",
|
||||||
|
"plugin": {
|
||||||
|
"plugin": "grafana-testdata-datasource"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/apis/testdata.datasource.grafana.app/v0alpha1/": {
|
"/apis/testdata.datasource.grafana.app/v0alpha1/": {
|
||||||
@@ -377,7 +380,7 @@
|
|||||||
"DataSource"
|
"DataSource"
|
||||||
],
|
],
|
||||||
"description": "Query the TestData datasources",
|
"description": "Query the TestData datasources",
|
||||||
"operationId": "createDataSourceQuery",
|
"operationId": "queryDataSource",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
@@ -410,7 +413,7 @@
|
|||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"description": "name of the QueryDataResponse",
|
"description": "DataSource identifier",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -430,36 +433,462 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource": {
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource": {
|
||||||
|
"summary": "hello world",
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"DataSource"
|
"Route"
|
||||||
],
|
],
|
||||||
"description": "Get resources in the datasource plugin. NOTE, additional routes may exist, but are not exposed via OpenAPI",
|
"operationId": "get_route",
|
||||||
"operationId": "getDataSourceResource",
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"default": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"*/*": {
|
"text/plain": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"x-kubernetes-action": "connect",
|
|
||||||
"x-kubernetes-group-version-kind": {
|
|
||||||
"group": "testdata.datasource.grafana.app",
|
|
||||||
"version": "v0alpha1",
|
|
||||||
"kind": "Status"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"description": "name of the Status",
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/boom": {
|
||||||
|
"summary": "force a panic",
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "get_route_boom",
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "post_route_boom",
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/scenarios": {
|
||||||
|
"summary": "hello world",
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "get_route_scenarios",
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/sim/{key}": {
|
||||||
|
"description": "Get list of simulations",
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "get_route_sim_key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"in": "path",
|
||||||
|
"description": "simulation key (should include hz)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "post_route_sim_key",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"in": "path",
|
||||||
|
"description": "simulation key (should include hz)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/sims": {
|
||||||
|
"description": "Get list of simulations",
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "get_route_sims",
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/stream": {
|
||||||
|
"summary": "Get streaming response",
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "get_route_stream",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "count",
|
||||||
|
"in": "query",
|
||||||
|
"description": "number of points that will be returned",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"example": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "start",
|
||||||
|
"in": "query",
|
||||||
|
"description": "the start value",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flush",
|
||||||
|
"in": "query",
|
||||||
|
"description": "How often the result is flushed (1-100%)",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"example": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "speed",
|
||||||
|
"in": "query",
|
||||||
|
"description": "the clock cycle",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "100ms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "format",
|
||||||
|
"in": "query",
|
||||||
|
"description": "the response format",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"json",
|
||||||
|
"influx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/test": {
|
||||||
|
"summary": "Echo any request",
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "post_route_test",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"description": "object name and auth scope, such as for teams and projects",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource/test/json": {
|
||||||
|
"summary": "Echo json request",
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Route"
|
||||||
|
],
|
||||||
|
"operationId": "post_route_test_json",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"description": "name of the DataSource",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -530,6 +959,26 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"DataSourceSpec": {
|
||||||
|
"description": "Test data does not require any explicit configuration",
|
||||||
|
"required": [
|
||||||
|
"title"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"description": "display name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"description": "not used",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"example": {
|
||||||
|
"url": "http://xxxx"
|
||||||
|
}
|
||||||
|
},
|
||||||
"QueryRequestSchema": {
|
"QueryRequestSchema": {
|
||||||
"description": "Schema for a set of queries sent to the query method",
|
"description": "Schema for a set of queries sent to the query method",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -650,12 +1099,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "DataSource configuration -- these properties are all visible to anyone able to query the data source from their browser",
|
"$ref": "#/components/schemas/DataSourceSpec"
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.datasource.v0alpha1.UnstructuredSpec"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-kubernetes-group-version-kind": [
|
"x-kubernetes-group-version-kind": [
|
||||||
|
|||||||
Reference in New Issue
Block a user