with hooks
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kube-openapi/pkg/validation/strfmt"
|
||||
"k8s.io/kube-openapi/pkg/validation/validate"
|
||||
|
||||
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
)
|
||||
|
||||
// Validate implements builder.APIGroupValidation.
|
||||
func (b *DataSourceAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||
fmt.Printf("Calling validate???\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mutate implements builder.APIGroupMutation.
|
||||
func (b *DataSourceAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||
obj := a.GetObject()
|
||||
|
||||
if b.specProvider == nil || obj == nil || a.GetOperation() == admission.Connect {
|
||||
return nil // This is normal for sub-resource
|
||||
}
|
||||
|
||||
ds, ok := obj.(*datasourceV0.DataSource)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected datasource object")
|
||||
}
|
||||
|
||||
info, err := b.specProvider()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.DataSourceSpec != nil {
|
||||
validate := validate.NewSchemaValidator(info.DataSourceSpec, nil, "", strfmt.Default)
|
||||
results := validate.Validate(ds.Spec) // will fail!
|
||||
for _, err := range results.Errors {
|
||||
fmt.Printf("ERROR: %+v\n", err)
|
||||
}
|
||||
for _, err := range results.Warnings {
|
||||
fmt.Printf("WARNING: %+v\n", err)
|
||||
}
|
||||
}
|
||||
if len(info.SecureValues) > 0 {
|
||||
fmt.Printf("TODO, validate secure values")
|
||||
}
|
||||
|
||||
// TODO... call the plugin mutation hook
|
||||
return nil // TODO! replace with app helpers
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
secretsV0 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query/queryschema"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
@@ -41,7 +40,11 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
|
||||
ds.Properties["apiVersion"] = *spec.StringProperty().WithEnum(b.GetGroupVersion().String())
|
||||
ds.Properties["kind"] = *spec.StringProperty().WithEnum("DataSource")
|
||||
|
||||
custom, err := getCustomOpenAPI(b.pluginJSON.ID)
|
||||
if b.specProvider == nil {
|
||||
return oas, nil
|
||||
}
|
||||
|
||||
custom, err := b.specProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,236 +129,3 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
|
||||
}
|
||||
return oas, err
|
||||
}
|
||||
|
||||
func getCustomOpenAPI(plugin string) (*datasourceV0.DataSourceOpenAPIExtension, error) {
|
||||
if plugin == "grafana-testdata-datasource" {
|
||||
oas := &datasourceV0.DataSourceOpenAPIExtension{
|
||||
SecureValues: []datasourceV0.SecureValueInfo{{
|
||||
Key: "aaa",
|
||||
Description: "describe aaa",
|
||||
Required: true,
|
||||
}, {
|
||||
Key: "bbb",
|
||||
Description: "describe bbb",
|
||||
}},
|
||||
}
|
||||
|
||||
// Dummy spec
|
||||
p := &spec.Schema{} //SchemaProps: spec.SchemaProps{Type: []string{"object"}}}
|
||||
p.Description = "HELLO!"
|
||||
p.Required = []string{"url"}
|
||||
p.AdditionalProperties = &spec.SchemaOrBool{Allows: false}
|
||||
p.Properties = map[string]spec.Schema{
|
||||
"url": *spec.StringProperty(),
|
||||
"str": *spec.StringProperty(), // ??? must this be under jsonData?
|
||||
"int64": *spec.Int64Property(), // ??? must this be under jsonData?
|
||||
}
|
||||
p.Example = map[string]any{
|
||||
"url": "http://xxxx",
|
||||
"int64": 1234,
|
||||
}
|
||||
oas.DataSourceSpec = p
|
||||
|
||||
// Resource routes
|
||||
// https://github.com/grafana/grafana/blob/main/pkg/tsdb/grafana-testdata-datasource/resource_handler.go#L20
|
||||
unstructured := spec.RefProperty("#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured")
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mux.HandleFunc("/sims", s.sims.GetSimulationHandler)
|
||||
// mux.HandleFunc("/sim/", s.sims.GetSimulationHandler)
|
||||
|
||||
// Duplicate the test route (but with a new path)
|
||||
testcopy := *oas.Routes["/test"]
|
||||
oas.Routes["/test/json"] = &testcopy
|
||||
|
||||
return oas, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -27,10 +27,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds"
|
||||
)
|
||||
|
||||
var _ builder.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
|
||||
var (
|
||||
_ builder.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
|
||||
_ builder.APIGroupMutation = (*DataSourceAPIBuilder)(nil)
|
||||
_ builder.APIGroupValidation = (*DataSourceAPIBuilder)(nil)
|
||||
)
|
||||
|
||||
// DataSourceAPIBuilder is used just so wire has something unique to return
|
||||
type DataSourceAPIBuilder struct {
|
||||
@@ -41,6 +46,7 @@ type DataSourceAPIBuilder struct {
|
||||
datasources PluginDatasourceProvider
|
||||
contextProvider PluginContextWrapper
|
||||
accessControl accesscontrol.AccessControl
|
||||
specProvider func() (*datasourceV0.DataSourceOpenAPIExtension, error) // TODO? include query types
|
||||
queryTypes *queryV0.QueryTypeDefinitionList
|
||||
log log.Logger
|
||||
}
|
||||
@@ -91,6 +97,12 @@ func RegisterAPIService(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// HARDCODE spec access
|
||||
if ds.ID == "grafana-testdata-datasource" {
|
||||
builder.specProvider = testdatasource.OpenAPIExtension
|
||||
}
|
||||
|
||||
apiRegistrar.RegisterAPI(builder)
|
||||
}
|
||||
return builder, nil // only used for wire
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
apiVersion: testdata.datasource.grafana.app/v0alpha1
|
||||
kind: GenericDataSource
|
||||
kind: DataSource
|
||||
metadata:
|
||||
name: sample-testdata
|
||||
spec:
|
||||
@@ -14,4 +14,4 @@ secure:
|
||||
sampleA:
|
||||
create: secret value here # replaced with UID on write
|
||||
sampleB:
|
||||
uid: XYZ # will not exist
|
||||
name: XYZ # reference to a existing secret
|
||||
@@ -0,0 +1,236 @@
|
||||
package testdatasource
|
||||
|
||||
import (
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
)
|
||||
|
||||
func OpenAPIExtension() (*datasourceV0.DataSourceOpenAPIExtension, error) {
|
||||
oas := &datasourceV0.DataSourceOpenAPIExtension{
|
||||
SecureValues: []datasourceV0.SecureValueInfo{{
|
||||
Key: "aaa",
|
||||
Description: "describe aaa",
|
||||
Required: true,
|
||||
}, {
|
||||
Key: "bbb",
|
||||
Description: "describe bbb",
|
||||
}},
|
||||
}
|
||||
|
||||
// Dummy spec
|
||||
p := &spec.Schema{} //SchemaProps: spec.SchemaProps{Type: []string{"object"}}}
|
||||
p.Description = "HELLO!"
|
||||
p.Required = []string{"url"}
|
||||
p.AdditionalProperties = &spec.SchemaOrBool{Allows: false}
|
||||
p.Properties = map[string]spec.Schema{
|
||||
"url": *spec.StringProperty(),
|
||||
"str": *spec.StringProperty(), // ??? must this be under jsonData?
|
||||
"int64": *spec.Int64Property(), // ??? must this be under jsonData?
|
||||
}
|
||||
p.Example = map[string]any{
|
||||
"url": "http://xxxx",
|
||||
"int64": 1234,
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Duplicate the test route (but with a new path)
|
||||
testcopy := *oas.Routes["/test"]
|
||||
oas.Routes["/test/json"] = &testcopy
|
||||
|
||||
return oas, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package testdatasource
|
||||
|
||||
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 := OpenAPIExtension()
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user