Compare commits

...

139 Commits

Author SHA1 Message Date
Ryan McKinley
352ac8fc5b lint 2025-12-31 11:57:21 +03:00
Ryan McKinley
00917091d7 remove version from snapshot 2025-12-31 10:27:50 +03:00
Ryan McKinley
d73e4a229f Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-12-31 10:08:44 +03:00
Ryan McKinley
96e3fdbfd5 add plugin iniformation 2025-12-18 10:42:50 +03:00
Ryan McKinley
68a65af091 Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-12-18 10:31:57 +03:00
Ryan McKinley
82c045e501 Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-12-17 13:42:34 +03:00
Ryan McKinley
80d806a822 merge main 2025-12-15 08:52:54 +03:00
Ryan McKinley
570146fb36 openapi 2025-12-09 15:39:01 +03:00
Ryan McKinley
61ec394f59 replace 2025-12-09 15:20:31 +03:00
Ryan McKinley
ad0adf79bd fix test spec 2025-12-09 15:08:16 +03:00
Ryan McKinley
1f396581a6 with spec 2025-12-09 15:01:33 +03:00
Ryan McKinley
406502f351 merge main 2025-12-09 14:25:29 +03:00
Ryan McKinley
9648c0956f merge main
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-11-17 13:44:20 +03:00
Ryan McKinley
008d373f7a fix lint
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-10-26 22:03:58 +03:00
Ryan McKinley
3f1145fe3b merge main 2025-10-26 21:35:49 +03:00
Ryan McKinley
09c5311797 merge main 2025-10-26 21:01:10 +03:00
Ryan McKinley
6df663584c add conversion
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-10-16 16:14:27 +03:00
Ryan McKinley
2614a917de Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-10-16 16:08:13 +03:00
Ryan McKinley
a1015f7a9f Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-10-16 16:04:44 +03:00
Ryan McKinley
f17765b70c add comment 2025-10-16 15:54:54 +03:00
Ryan McKinley
9a05906299 add operations test 2025-10-16 15:49:37 +03:00
Ryan McKinley
969ae75b08 update openapi specs 2025-10-16 15:20:30 +03:00
Ryan McKinley
eeb6d105ed update openapi specs 2025-10-16 15:18:53 +03:00
Ryan McKinley
b93fb964b7 Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-10-16 14:56:32 +03:00
Ryan McKinley
d1657d4684 merge main
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-10-14 15:48:41 +03:00
Ryan McKinley
63374d29c0 merge main
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-10-08 12:54:38 +03:00
Ryan McKinley
379aff5ff4 merge main
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-28 22:36:57 +03:00
Ryan McKinley
8bf8c07878 Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-08-28 22:31:03 +03:00
Ryan McKinley
f7dc2f6e56 Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder 2025-08-27 14:06:47 +03:00
Ryan McKinley
704f533846 generic apiserver 2025-08-27 12:51:17 +03:00
Ryan McKinley
82312c3418 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-27 12:50:11 +03:00
Ryan McKinley
87a9f26997 lint 2025-08-27 10:07:45 +03:00
Ryan McKinley
1fe4415682 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-27 10:04:41 +03:00
Ryan McKinley
a629b70c1f Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-27 00:50:13 +03:00
Ryan McKinley
36fe8c6b61 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-26 12:36:14 +03:00
Ryan McKinley
e96dd1b12a comments 2025-08-26 12:21:54 +03:00
Ryan McKinley
a4adcf8896 better comment 2025-08-26 12:09:23 +03:00
Ryan McKinley
1671a8644f add feature toggle 2025-08-26 11:31:02 +03:00
Ryan McKinley
bfcf649e8b remove plugin 2025-08-26 11:21:10 +03:00
Ryan McKinley
ebb4cfadff full coverage for convert 2025-08-26 10:56:36 +03:00
Ryan McKinley
29e9ae1918 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-26 10:51:34 +03:00
Ryan McKinley
540eb6c862 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-26 08:41:07 +03:00
Ryan McKinley
17b20fb464 make update-workspace
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-22 20:58:45 +03:00
Ryan McKinley
dc986afd68 fix build 2025-08-22 20:51:11 +03:00
Ryan McKinley
eba83d8973 feedback
Some checks failed
CodeQL checks / Detect whether code changed (push) Has been cancelled
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-22 20:44:19 +03:00
Ryan McKinley
cf89fb2a13 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-22 20:24:22 +03:00
Ryan McKinley
290e8a97f1 merge main 2025-08-22 20:05:53 +03:00
Ryan McKinley
0ed24434c2 merge main
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-21 14:54:26 +03:00
Ryan McKinley
cbfb1e15ed Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-21 14:50:30 +03:00
Ryan McKinley
47e379c8d9 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-21 14:27:49 +03:00
Ryan McKinley
500b029b25 exclude false values
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-20 11:37:45 +03:00
Ryan McKinley
bce28b8663 unstructured spec
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-20 10:39:14 +03:00
Ryan McKinley
b0c9350580 unstructured spec 2025-08-20 10:38:52 +03:00
Ryan McKinley
76b4c687b0 update comments 2025-08-20 09:31:41 +03:00
Ryan McKinley
0d6f718255 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-20 09:13:30 +03:00
Ryan McKinley
6b72ddb4d3 cleanup
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-19 23:50:26 +03:00
Ryan McKinley
2f094fdcd9 exclude empty secure values 2025-08-19 23:41:13 +03:00
Ryan McKinley
6a0ce01d18 more comments 2025-08-19 23:21:43 +03:00
Ryan McKinley
d00b8ab76d Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-19 23:17:31 +03:00
Ryan McKinley
e577b8d0b8 rename ListDataSources 2025-08-19 22:50:50 +03:00
Ryan McKinley
2270d6cb22 remove dummy validation 2025-08-19 22:01:58 +03:00
Ryan McKinley
c539be48d8 remove dummy validatio 2025-08-19 21:23:32 +03:00
Ryan McKinley
03a2153bd8 merge main 2025-08-19 20:31:38 +03:00
Ryan McKinley
eb01a3e462 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-19 20:25:01 +03:00
Ryan McKinley
f6b6b62f5e update name comment
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-19 13:03:02 +03:00
Ryan McKinley
5a2351387a Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-19 11:24:22 +03:00
Ryan McKinley
78d9829d3b Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-06 23:44:34 +03:00
Ryan McKinley
d9e1adaa48 rename connections
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-08-05 14:09:07 +03:00
Ryan McKinley
949b521ac7 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-08-05 14:04:48 +03:00
Ryan McKinley
1d9572cdb2 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-31 21:00:39 +02:00
Ryan McKinley
f5e649183b do not expose OpenAPI unless it exists
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-29 13:29:28 +02:00
Ryan McKinley
4b44a83802 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-29 13:06:06 +02:00
Ryan McKinley
26dc101ecc Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-28 15:42:32 +02:00
Ryan McKinley
ca2f2e8d9e Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-28 12:50:03 +02:00
Ryan McKinley
0e10d9cd16 merge main
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-28 12:06:25 +02:00
Ryan McKinley
ed9789179b Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-17 10:21:38 -07:00
Ryan McKinley
5890137232 fix unique name constraint
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-11 12:52:23 -07:00
Ryan McKinley
471c4eb89d fix unique name constraint 2025-07-11 12:50:47 -07:00
Ryan McKinley
3ac63ea9c4 update comments 2025-07-11 12:08:12 -07:00
Ryan McKinley
7d2d38fd94 merge main 2025-07-11 09:16:23 -07:00
Ryan McKinley
80dda87868 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-11 09:16:19 -07:00
Ryan McKinley
04a4fd7c61 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-10 14:54:39 -07:00
Ryan McKinley
1d26b455fd use common secure values 2025-07-10 14:39:14 -07:00
Ryan McKinley
8f0109a1ee Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-10 14:29:04 -07:00
Ryan McKinley
798e9a32fc unstructured IsZero
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-10 12:46:33 -07:00
Ryan McKinley
f4001d7cdc Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-10 11:59:23 -07:00
Ryan McKinley
718c28438e avoid lint issue 2025-07-10 10:00:36 -07:00
Ryan McKinley
b58b9144a5 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-10 09:51:51 -07:00
Ryan McKinley
96830de552 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-09 14:35:20 -07:00
Ryan McKinley
58c956eb19 revert depguard config changes
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-08 18:03:57 -07:00
Ryan McKinley
b1d8e9ca41 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-08 18:00:28 -07:00
Ryan McKinley
4006a61964 update go.mod
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-08 07:10:27 -07:00
Ryan McKinley
0a24935e45 depguard 2025-07-08 07:09:40 -07:00
Ryan McKinley
f25c9b0e03 merge main 2025-07-08 06:29:45 -07:00
Ryan McKinley
5c478e98c4 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-08 06:29:39 -07:00
Ryan McKinley
ddc99a1dca Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-07 19:06:12 -07:00
Ryan McKinley
e12d1ba6ca Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-07 13:07:20 -07:00
Ryan McKinley
420e070aea Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-07 10:38:32 -07:00
Ryan McKinley
732d4351de with hooks
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-03 17:44:44 -07:00
Ryan McKinley
f842bb7af7 merge main 2025-07-03 15:01:11 -07:00
Ryan McKinley
a1e2ba6617 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-03 14:58:45 -07:00
Ryan McKinley
1cade082fc now with routes for testdata 2025-07-03 14:43:12 -07:00
Ryan McKinley
ea38c4ad5a custom jsonschema for testdata 2025-07-03 13:03:41 -07:00
Ryan McKinley
01926ab3c8 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-03 10:30:38 -07:00
Ryan McKinley
84a5282ea2 fix test 2025-07-03 10:21:28 -07:00
Ryan McKinley
35d7bb880a Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-03 10:18:39 -07:00
Ryan McKinley
251b7b4b4e hardcode custom spec
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-02 23:08:20 -07:00
Ryan McKinley
4e3197a58f fix spelling 2025-07-02 19:40:19 -07:00
Ryan McKinley
1a883d1167 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-02 19:34:48 -07:00
Ryan McKinley
cc2e96a558 update openapi specs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-02 14:58:34 -07:00
Ryan McKinley
6b2ebb2d65 lint 2025-07-02 14:43:37 -07:00
Ryan McKinley
7cc36672bb fix integration test 2025-07-02 14:41:52 -07:00
Ryan McKinley
5be096833a run codegen 2025-07-02 14:27:31 -07:00
Ryan McKinley
076d1a5ad5 revert go.mod changes 2025-07-02 13:53:22 -07:00
Ryan McKinley
775ed81b58 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-02 13:37:12 -07:00
Ryan McKinley
fb0aaa321e merge main 2025-07-02 13:33:33 -07:00
Ryan McKinley
c1c1f3a85c merge main 2025-07-02 10:06:43 -07:00
Ryan McKinley
c0f1a6423c Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-02 09:56:20 -07:00
Ryan McKinley
a44fdee0ef merge upstream service changes
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-01 17:00:04 -07:00
Ryan McKinley
97a089ef05 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-07-01 16:53:45 -07:00
Ryan McKinley
b2799f977f use config from secrets service
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-07-01 13:44:47 -07:00
Ryan McKinley
2d4fd99e7a update openapi 2025-07-01 09:14:01 -07:00
Ryan McKinley
c4842845e7 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-06-30 13:47:39 -07:00
Ryan McKinley
e9105e6303 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-06-27 20:08:48 -07:00
Ryan McKinley
841e0bd5e5 update openapi 2025-06-27 16:29:22 -07:00
Ryan McKinley
5c80bc12f0 organize models 2025-06-27 16:03:59 -07:00
Ryan McKinley
60981e813b organize models 2025-06-27 16:03:24 -07:00
Ryan McKinley
94eea040e2 Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-06-27 15:15:34 -07:00
Ryan McKinley
6869160e97 merge main
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-06-27 12:49:54 -07:00
Ryan McKinley
4dc6ad9257 use plural
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-06-20 10:14:18 +03:00
Ryan McKinley
b2c368e1cd use plural 2025-06-20 10:09:34 +03:00
Ryan McKinley
1ea3b5440c secure value update
Some checks failed
CodeQL checks / Analyze (actions) (push) Has been cancelled
CodeQL checks / Analyze (go) (push) Has been cancelled
CodeQL checks / Analyze (javascript) (push) Has been cancelled
2025-06-20 00:18:14 +03:00
Ryan McKinley
9bbe311bdf Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs 2025-06-20 00:18:05 +03:00
Ryan McKinley
5a338adf47 add basic tests 2025-06-19 23:42:39 +03:00
Ryan McKinley
1d218d01b2 wire up dual writer 2025-06-19 20:19:48 +03:00
Ryan McKinley
ae6ba4ba44 fill stubs for cloud config 2025-06-19 20:05:36 +03:00
Ryan McKinley
90614fcb91 use readme 2025-06-19 17:57:05 +03:00
Ryan McKinley
1bedd399d6 with configs 2025-06-19 17:33:38 +03:00
Ryan McKinley
0bff610506 with configs 2025-06-19 17:27:40 +03:00
15 changed files with 1198 additions and 69 deletions

View 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"`
}

View File

@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"log"
"reflect"
)
// returns the current implementation version
@@ -117,6 +118,23 @@ func (j *Json) Interface() any {
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`
func (j *Json) Encode() ([]byte, error) {
return j.MarshalJSON()

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSimplejson(t *testing.T) {
@@ -272,3 +273,45 @@ func TestMustJson(t *testing.T) {
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())
})
}
}

View File

@@ -55,19 +55,33 @@ func (r *converter) asDataSource(ds *datasources.DataSource) (*datasourceV0.Data
SetBasicAuthUser(ds.BasicAuthUser).
SetWithCredentials(ds.WithCredentials).
SetIsDefault(ds.IsDefault).
SetReadOnly(ds.ReadOnly).
SetJSONData(ds.JsonData)
SetReadOnly(ds.ReadOnly)
if ds.JsonData != nil && !ds.JsonData.IsEmpty() {
obj.Spec.SetJSONData(ds.JsonData.Interface())
}
rv := int64(0)
if !ds.Created.IsZero() {
obj.CreationTimestamp = metav1.NewTime(ds.Created)
rv = ds.Created.UnixMilli()
}
// Only mark updated if the times have actually changed
if !ds.Updated.IsZero() {
obj.ResourceVersion = fmt.Sprintf("%d", ds.Updated.UnixMilli())
obj.Annotations = map[string]string{
utils.AnnoKeyUpdatedTimestamp: ds.Updated.Format(time.RFC3339),
rv = ds.Updated.UnixMilli()
delta := rv - obj.CreationTimestamp.UnixMilli()
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 != "" {
obj.APIVersion = fmt.Sprintf("%s/%s", r.group, ds.APIVersion)
}

View 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
}

View 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))
}

View File

@@ -2,6 +2,7 @@ package datasource
import (
"context"
"errors"
"fmt"
"time"
@@ -9,11 +10,15 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"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/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
"github.com/grafana/grafana/pkg/services/datasources"
)
var (
@@ -84,7 +89,16 @@ func (s *legacyStorage) Create(ctx context.Context, obj runtime.Object, createVa
if !ok {
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.

View File

@@ -2,17 +2,35 @@ package datasource
import (
"fmt"
"maps"
"slices"
"strings"
"k8s.io/kube-openapi/pkg/spec3"
"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/services/apiserver/builder"
)
func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
// The plugin 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
root := "/apis/" + b.datasourceResourceInfo.GroupVersion().String() + "/"
@@ -28,6 +46,17 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
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
prefix := root + "namespaces/{namespace}/datasources/{name}/resource"
r := oas.Paths.Paths[prefix]
@@ -52,7 +81,7 @@ func (b *DataSourceAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.Op
// Mark connections as deprecated
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 {
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
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager/sources"
"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/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
@@ -39,14 +40,16 @@ var (
// DataSourceAPIBuilder is used just so wire has something unique to return
type DataSourceAPIBuilder struct {
datasourceResourceInfo utils.ResourceInfo
pluginJSON plugins.JSONData
client PluginClient // will only ever be called with the same plugin id!
datasources PluginDatasourceProvider
contextProvider PluginContextWrapper
accessControl accesscontrol.AccessControl
queryTypes *queryV0.QueryTypeDefinitionList
configCrudUseNewApis bool
dataSourceCRUDMetric *prometheus.HistogramVec
pluginJSON plugins.JSONData
client PluginClient // will only ever be called with the same plugin id!
datasources PluginDatasourceProvider
contextProvider PluginContextWrapper
accessControl accesscontrol.AccessControl
queryTypes *queryV0.QueryTypeDefinitionList
schemaProvider func() (*datasourceV0.DataSourceOpenAPIExtension, error)
configCrudUseNewApis bool
dataSourceCRUDMetric *prometheus.HistogramVec
}
func RegisterAPIService(
@@ -59,8 +62,17 @@ func RegisterAPIService(
reg prometheus.Registerer,
pluginSources sources.Registry,
) (*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
if !features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections) && !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
if !configCrudUseNewApis && !isExperimental {
return nil, nil
}
@@ -82,28 +94,33 @@ func RegisterAPIService(
return nil, fmt.Errorf("error getting list of datasource plugins: %s", err)
}
for _, pluginJSON := range pluginJSONs {
client, ok := pluginClient.(PluginClient)
if !ok {
return nil, fmt.Errorf("plugin client is not a PluginClient: %T", pluginClient)
}
// For the ST runner, the client can talk to any plugin
client, ok := pluginClient.(PluginClient)
if !ok {
return nil, fmt.Errorf("plugin client is not a PluginClient: %T", pluginClient)
}
for _, pluginJSON := range pluginJSONs {
builder, err = NewDataSourceAPIBuilder(
pluginJSON,
client,
datasources.GetDatasourceProvider(pluginJSON),
contextProvider,
accessControl,
//nolint:staticcheck // not yet migrated to OpenFeature
features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes),
//nolint:staticcheck // not yet migrated to OpenFeature
features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections),
useQueryTypes, // Exposes the query type OpenAPI schema
configCrudUseNewApis, // Enables the new connections-based datasource config CRUD APIs
)
if err != nil {
return nil, err
}
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)
}
return builder, nil // only used for wire
@@ -247,6 +264,7 @@ func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
return err
}
} else {
// Read-only access to datasource connection info
storage[ds.StoragePath()] = &connectionAccess{
datasources: b.datasources,
resourceInfo: ds,

View File

@@ -3,6 +3,7 @@
"name": "unique-identifier",
"namespace": "org-0",
"uid": "YpaSG5GQAdxtLZtF6BqQWCeYXOhbVi5C4Cg4oILnJC0X",
"resourceVersion": "1015203600000",
"generation": 8,
"creationTimestamp": "2002-03-04T01:00:00Z",
"labels": {
@@ -10,7 +11,6 @@
}
},
"spec": {
"jsonData": null,
"title": "Display name"
}
}

View File

@@ -5,7 +5,6 @@
"uid": "boDNh7zU3nXj46rOXIJI7r44qaxjs8yy9I9dOj1MyBoX"
},
"spec": {
"jsonData": null,
"title": "Hello testdata"
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"maps"
"net/http"
"strings"
"sync"
@@ -232,11 +233,9 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s
parent := copy.Paths.Paths[path[:idx+6]]
if parent != nil && parent.Get != nil {
for _, op := range GetPathOperations(spec) {
if op != nil && op.Extensions != nil {
action, ok := op.Extensions.GetString("x-kubernetes-action")
if ok && action == "connect" {
op.Tags = parent.Get.Tags
}
action, ok := op.Extensions.GetString("x-kubernetes-action")
if ok && action == "connect" {
op.Tags = parent.Get.Tags
}
}
}
@@ -249,15 +248,32 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s
}
}
func GetPathOperations(path *spec3.Path) []*spec3.Operation {
return []*spec3.Operation{
path.Get,
path.Head,
path.Delete,
path.Patch,
path.Post,
path.Put,
path.Trace,
path.Options,
// GetPathOperations returns the set of non-nil operations defined on a path
func GetPathOperations(path *spec3.Path) map[string]*spec3.Operation {
ops := make(map[string]*spec3.Operation)
if path.Get != nil {
ops[http.MethodGet] = path.Get
}
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
}

View 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)
}
})
}
}

View File

@@ -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))
}
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, rsp.Body, "", " ")
schema := map[string]any{}
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)
pretty := prettyJSON.String()
write := false
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
body, err := os.ReadFile(fpath)
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.Fail()
write = true
@@ -896,7 +905,7 @@ func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h
}
if write {
e2 := os.WriteFile(fpath, []byte(pretty), 0o644)
e2 := os.WriteFile(fpath, pretty, 0o644)
if e2 != nil {
t.Errorf("error writing file: %s", e2.Error())
}

View File

@@ -2,7 +2,10 @@
"openapi": "3.0.0",
"info": {
"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": {
"/apis/testdata.datasource.grafana.app/v0alpha1/": {
@@ -377,7 +380,7 @@
"DataSource"
],
"description": "Query the TestData datasources",
"operationId": "createDataSourceQuery",
"operationId": "queryDataSource",
"requestBody": {
"content": {
"application/json": {
@@ -410,7 +413,7 @@
{
"name": "name",
"in": "path",
"description": "name of the QueryDataResponse",
"description": "DataSource identifier",
"required": true,
"schema": {
"type": "string",
@@ -430,36 +433,462 @@
]
},
"/apis/testdata.datasource.grafana.app/v0alpha1/namespaces/{namespace}/datasources/{name}/resource": {
"summary": "hello world",
"get": {
"tags": [
"DataSource"
"Route"
],
"description": "Get resources in the datasource plugin. NOTE, additional routes may exist, but are not exposed via OpenAPI",
"operationId": "getDataSourceResource",
"operationId": "get_route",
"responses": {
"200": {
"description": "OK",
"default": {
"content": {
"*/*": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
},
"x-kubernetes-action": "connect",
"x-kubernetes-group-version-kind": {
"group": "testdata.datasource.grafana.app",
"version": "v0alpha1",
"kind": "Status"
}
},
"parameters": [
{
"name": "name",
"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,
"schema": {
"type": "string",
@@ -530,6 +959,26 @@
},
"components": {
"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": {
"description": "Schema for a set of queries sent to the query method",
"type": "object",
@@ -650,12 +1099,7 @@
}
},
"spec": {
"description": "DataSource configuration -- these properties are all visible to anyone able to query the data source from their browser",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.datasource.v0alpha1.UnstructuredSpec"
}
]
"$ref": "#/components/schemas/DataSourceSpec"
}
},
"x-kubernetes-group-version-kind": [