-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experimental: Add query type definition and schemas (#897)
- Loading branch information
Showing
34 changed files
with
3,743 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
## APIServer APIs | ||
|
||
This package aims to expose types from the plugins-sdk in the grafana apiserver. | ||
|
||
Currently, the types are not useable directly so we can avoid adding a dependency on k8s.io/apimachinery | ||
until it is more necessary. See https://github.com/grafana/grafana-plugin-sdk-go/pull/909 | ||
|
||
The "v0alpha1" version should be considered experimental and is subject to change at any time without notice. | ||
Once it is more stable, it will be released as a versioned API (v1) | ||
|
||
|
||
### Codegen | ||
|
||
The file [apis/data/v0alpha1/zz_generated.deepcopy.go](data/v0alpha1/zz_generated.deepcopy.go) was generated by copying the folder structure into | ||
https://github.com/grafana/grafana/tree/main/pkg/apis and then run `hack/update-codegen.sh data` in [hack scripts](https://github.com/grafana/grafana/tree/v10.3.3/hack). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package v0alpha1 | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/grafana/grafana-plugin-sdk-go/backend" | ||
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" | ||
) | ||
|
||
type QueryDataClient interface { | ||
QueryData(ctx context.Context, req QueryDataRequest) (int, *backend.QueryDataResponse, error) | ||
} | ||
|
||
type simpleHTTPClient struct { | ||
url string | ||
client *http.Client | ||
headers map[string]string | ||
} | ||
|
||
func NewQueryDataClient(url string, client *http.Client, headers map[string]string) QueryDataClient { | ||
if client == nil { | ||
client = http.DefaultClient | ||
} | ||
return &simpleHTTPClient{ | ||
url: url, | ||
client: client, | ||
headers: headers, | ||
} | ||
} | ||
|
||
func (c *simpleHTTPClient) QueryData(ctx context.Context, query QueryDataRequest) (int, *backend.QueryDataResponse, error) { | ||
body, err := json.Marshal(query) | ||
if err != nil { | ||
return http.StatusBadRequest, nil, err | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, bytes.NewBuffer(body)) | ||
if err != nil { | ||
return http.StatusBadRequest, nil, err | ||
} | ||
for k, v := range c.headers { | ||
req.Header.Set(k, v) | ||
} | ||
req.Header.Set("Content-Type", "application/json") | ||
|
||
rsp, err := c.client.Do(req) | ||
if err != nil { | ||
return rsp.StatusCode, nil, err | ||
} | ||
defer rsp.Body.Close() | ||
|
||
qdr := &backend.QueryDataResponse{} | ||
iter, err := jsoniter.Parse(jsoniter.ConfigCompatibleWithStandardLibrary, rsp.Body, 1024*10) | ||
if err == nil { | ||
err = iter.ReadVal(qdr) | ||
} | ||
return rsp.StatusCode, qdr, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package v0alpha1_test | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestQueryClient(t *testing.T) { | ||
t.Skip() | ||
|
||
client := v0alpha1.NewQueryDataClient("http://localhost:3000/api/ds/query", nil, | ||
map[string]string{ | ||
"Authorization": "Bearer XYZ", | ||
}) | ||
body := `{ | ||
"from": "", | ||
"to": "", | ||
"queries": [ | ||
{ | ||
"refId": "X", | ||
"scenarioId": "csv_content", | ||
"datasource": { | ||
"type": "grafana-testdata-datasource", | ||
"uid": "PD8C576611E62080A" | ||
}, | ||
"csvContent": "a,b,c\n1,hello,true", | ||
"hide": true | ||
} | ||
] | ||
}` | ||
qdr := v0alpha1.QueryDataRequest{} | ||
err := json.Unmarshal([]byte(body), &qdr) | ||
require.NoError(t, err) | ||
|
||
code, rsp, err := client.QueryData(context.Background(), qdr) | ||
require.NoError(t, err) | ||
require.Equal(t, http.StatusOK, code) | ||
|
||
r, ok := rsp.Responses["X"] | ||
require.True(t, ok) | ||
|
||
for _, frame := range r.Frames { | ||
txt, err := frame.StringTable(20, 10) | ||
require.NoError(t, err) | ||
fmt.Printf("%s\n", txt) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// +k8s:deepcopy-gen=package | ||
// +k8s:openapi-gen=true | ||
// +k8s:defaulter-gen=TypeMeta | ||
// +groupName=data.grafana.com | ||
|
||
package v0alpha1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package v0alpha1 | ||
|
||
// ObjectMeta is a struct that aims to "look" like a real kubernetes object when | ||
// written to JSON, however it does not require the pile of dependencies | ||
// This is really an internal helper until we decide which dependencies make sense | ||
// to require within the SDK | ||
type ObjectMeta struct { | ||
// The name is for k8s and description, but not used in the schema | ||
Name string `json:"name,omitempty"` | ||
// Changes indicate that *something * changed | ||
ResourceVersion string `json:"resourceVersion,omitempty"` | ||
// Timestamp | ||
CreationTimestamp string `json:"creationTimestamp,omitempty"` | ||
} | ||
|
||
type TypeMeta struct { | ||
Kind string `json:"kind"` // "QueryTypeDefinitionList", | ||
APIVersion string `json:"apiVersion"` // "query.grafana.app/v0alpha1", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package v0alpha1 | ||
|
||
import ( | ||
"embed" | ||
|
||
"k8s.io/kube-openapi/pkg/common" | ||
spec "k8s.io/kube-openapi/pkg/validation/spec" | ||
) | ||
|
||
//go:embed query.schema.json query.definition.schema.json | ||
var f embed.FS | ||
|
||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { | ||
return map[string]common.OpenAPIDefinition{ | ||
"github.com/grafana/grafana-plugin-sdk-go/backend.DataResponse": schemaDataResponse(ref), | ||
"github.com/grafana/grafana-plugin-sdk-go/data.Frame": schemaDataFrame(ref), | ||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataQuery": schemaDataQuery(ref), | ||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.QueryTypeDefinitionSpec": schemaQueryTypeDefinitionSpec(ref), | ||
} | ||
} | ||
|
||
// Individual response | ||
func schemaDataResponse(_ common.ReferenceCallback) common.OpenAPIDefinition { | ||
return common.OpenAPIDefinition{ | ||
Schema: spec.Schema{ | ||
SchemaProps: spec.SchemaProps{ | ||
Description: "todo... improve schema", | ||
Type: []string{"object"}, | ||
AdditionalProperties: &spec.SchemaOrBool{Allows: true}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func schemaDataFrame(_ common.ReferenceCallback) common.OpenAPIDefinition { | ||
return common.OpenAPIDefinition{ | ||
Schema: spec.Schema{ | ||
SchemaProps: spec.SchemaProps{ | ||
Description: "any object for now", | ||
Type: []string{"object"}, | ||
Properties: map[string]spec.Schema{}, | ||
AdditionalProperties: &spec.SchemaOrBool{Allows: true}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func schemaQueryTypeDefinitionSpec(_ common.ReferenceCallback) common.OpenAPIDefinition { | ||
s, _ := loadSchema("query.definition.schema.json") | ||
if s == nil { | ||
s = &spec.Schema{} | ||
} | ||
return common.OpenAPIDefinition{ | ||
Schema: *s, | ||
} | ||
} | ||
|
||
func schemaDataQuery(_ common.ReferenceCallback) common.OpenAPIDefinition { | ||
s, _ := DataQuerySchema() | ||
if s == nil { | ||
s = &spec.Schema{} | ||
} | ||
s.SchemaProps.Type = []string{"object"} | ||
s.SchemaProps.AdditionalProperties = &spec.SchemaOrBool{Allows: true} | ||
return common.OpenAPIDefinition{Schema: *s} | ||
} | ||
|
||
// Get the cached feature list (exposed as a k8s resource) | ||
func DataQuerySchema() (*spec.Schema, error) { | ||
return loadSchema("query.schema.json") | ||
} | ||
|
||
// Get the cached feature list (exposed as a k8s resource) | ||
func loadSchema(path string) (*spec.Schema, error) { | ||
body, err := f.ReadFile(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
s := &spec.Schema{} | ||
err = s.UnmarshalJSON(body) | ||
return s, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package v0alpha1 | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"k8s.io/kube-openapi/pkg/validation/spec" | ||
"k8s.io/kube-openapi/pkg/validation/strfmt" | ||
"k8s.io/kube-openapi/pkg/validation/validate" | ||
) | ||
|
||
func TestOpenAPI(t *testing.T) { | ||
//nolint:gocritic | ||
defs := GetOpenAPIDefinitions(func(path string) spec.Ref { // (unlambda: replace ¯\_(ツ)_/¯) | ||
return spec.MustCreateRef(path) // placeholder for tests | ||
}) | ||
|
||
def, ok := defs["github.com/grafana/grafana-plugin-sdk-go/backend.DataResponse"] | ||
require.True(t, ok) | ||
require.Empty(t, def.Dependencies) // not yet supported! | ||
|
||
validator := validate.NewSchemaValidator(&def.Schema, nil, "data", strfmt.Default) | ||
|
||
body, err := os.ReadFile("./testdata/sample_query_results.json") | ||
require.NoError(t, err) | ||
unstructured := make(map[string]any) | ||
err = json.Unmarshal(body, &unstructured) | ||
require.NoError(t, err) | ||
|
||
result := validator.Validate(unstructured) | ||
for _, err := range result.Errors { | ||
assert.NoError(t, err, "validation error") | ||
} | ||
for _, err := range result.Warnings { | ||
assert.NoError(t, err, "validation warning") | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
experimental/apis/data/v0alpha1/query.definition.schema.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
{ | ||
"$schema": "https://json-schema.org/draft-04/schema#", | ||
"properties": { | ||
"discriminators": { | ||
"items": { | ||
"properties": { | ||
"field": { | ||
"type": "string", | ||
"description": "DiscriminatorField is the field used to link behavior to this specific\nquery type. It is typically \"queryType\", but can be another field if necessary" | ||
}, | ||
"value": { | ||
"type": "string", | ||
"description": "The discriminator value" | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"type": "object", | ||
"required": [ | ||
"field", | ||
"value" | ||
] | ||
}, | ||
"type": "array", | ||
"description": "Multiple schemas can be defined using discriminators" | ||
}, | ||
"description": { | ||
"type": "string", | ||
"description": "Describe whe the query type is for" | ||
}, | ||
"schema": { | ||
"$ref": "https://json-schema.org/draft-04/schema#", | ||
"type": "object", | ||
"description": "The query schema represents the properties that can be sent to the API\nIn many cases, this may be the same properties that are saved in a dashboard\nIn the case where the save model is different, we must also specify a save model" | ||
}, | ||
"examples": { | ||
"items": { | ||
"properties": { | ||
"name": { | ||
"type": "string", | ||
"description": "Version identifier or empty if only one exists" | ||
}, | ||
"description": { | ||
"type": "string", | ||
"description": "Optionally explain why the example is interesting" | ||
}, | ||
"saveModel": { | ||
"additionalProperties": true, | ||
"type": "object", | ||
"description": "An example value saved that can be saved in a dashboard" | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"type": "object" | ||
}, | ||
"type": "array", | ||
"description": "Examples (include a wrapper) ideally a template!" | ||
}, | ||
"changelog": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array", | ||
"description": "Changelog defines the changed from the previous version\nAll changes in the same version *must* be backwards compatible\nOnly notable changes will be shown here, for the full version history see git!" | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"type": "object", | ||
"required": [ | ||
"schema", | ||
"examples" | ||
] | ||
} |
Oops, something went wrong.