Skip to content

Commit 6158ec5

Browse files
authored
API: Add ResourceConversion service (#1044)
1 parent fbe8fc3 commit 6158ec5

18 files changed

+1222
-476
lines changed

backend/admission.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ const (
1212

1313
// EndpointMutateAdmission friendly name for the mutate admission endpoint/handler.
1414
EndpointMutateAdmission Endpoint = "mutateAdmission"
15-
16-
// EndpointConvertObject friendly name for the convert object endpoint/handler.
17-
EndpointConvertObject Endpoint = "convertObject"
1815
)
1916

2017
// AdmissionHandler is an EXPERIMENTAL service that allows checking objects before they are saved
@@ -26,13 +23,10 @@ type AdmissionHandler interface {
2623
ValidateAdmission(context.Context, *AdmissionRequest) (*ValidationResponse, error)
2724
// MutateAdmission converts the input into an object that can be saved, or rejects the request
2825
MutateAdmission(context.Context, *AdmissionRequest) (*MutationResponse, error)
29-
// ConvertObject is called to covert objects between different versions
30-
ConvertObject(context.Context, *ConversionRequest) (*ConversionResponse, error)
3126
}
3227

3328
type ValidateAdmissionFunc func(context.Context, *AdmissionRequest) (*ValidationResponse, error)
3429
type MutateAdmissionFunc func(context.Context, *AdmissionRequest) (*MutationResponse, error)
35-
type ConvertObjectFunc func(context.Context, *ConversionRequest) (*ConversionResponse, error)
3630

3731
// Operation is the type of resource operation being checked for admission control
3832
// https://github.com/kubernetes/kubernetes/blob/v1.30.0/pkg/apis/admission/types.go#L158
@@ -69,18 +63,6 @@ type AdmissionRequest struct {
6963
OldObjectBytes []byte `json:"old_object_bytes,omitempty"`
7064
}
7165

72-
// ConversionRequest supports converting an object from on version to another
73-
type ConversionRequest struct {
74-
// NOTE: this may not include app or datasource instance settings depending on the request
75-
PluginContext PluginContext `json:"pluginContext,omitempty"`
76-
// The object kind
77-
Kind GroupVersionKind `json:"kind,omitempty"`
78-
// Object is the object in the request. This includes the full metadata envelope.
79-
ObjectBytes []byte `json:"object_bytes,omitempty"`
80-
// Target converted version
81-
TargetVersion string `json:"target_version,omitempty"`
82-
}
83-
8466
// Basic request to say if the validation was successful or not
8567
type ValidationResponse struct {
8668
// Allowed indicates whether or not the admission request was permitted.
@@ -115,17 +97,6 @@ type MutationResponse struct {
11597
ObjectBytes []byte `json:"object_bytes,omitempty"`
11698
}
11799

118-
type ConversionResponse struct {
119-
// Allowed indicates whether or not the admission request was permitted.
120-
Allowed bool `json:"allowed,omitempty"`
121-
// Result contains extra details into why an admission request was denied.
122-
// This field IS NOT consulted in any way if "Allowed" is "true".
123-
// +optional
124-
Result *StatusResult `json:"result,omitempty"`
125-
// Converted object bytes
126-
ObjectBytes []byte `json:"object_bytes,omitempty"`
127-
}
128-
129100
type StatusResult struct {
130101
// Status of the operation.
131102
// One of: "Success" or "Failure".

backend/admission_adapter.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,3 @@ func (a *admissionSDKAdapter) MutateAdmission(ctx context.Context, req *pluginv2
5050

5151
return ToProto().MutationResponse(resp), nil
5252
}
53-
54-
func (a *admissionSDKAdapter) ConvertObject(ctx context.Context, req *pluginv2.ConversionRequest) (*pluginv2.ConversionResponse, error) {
55-
ctx = setupContext(ctx, EndpointConvertObject)
56-
parsedReq := FromProto().ConversionRequest(req)
57-
58-
var resp *ConversionResponse
59-
err := wrapHandler(ctx, parsedReq.PluginContext, func(ctx context.Context) (RequestStatus, error) {
60-
var innerErr error
61-
resp, innerErr = a.handler.ConvertObject(ctx, parsedReq)
62-
return RequestStatusFromError(innerErr), innerErr
63-
})
64-
if err != nil {
65-
return nil, err
66-
}
67-
68-
return ToProto().ConversionResponse(resp), nil
69-
}

backend/app/manage.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ type ManageOpts struct {
2121

2222
// Stateless admission handler
2323
AdmissionHandler backend.AdmissionHandler
24+
25+
// Stateless conversion handler
26+
ConversionHandler backend.ConversionHandler
2427
}
2528

2629
// Manage starts serving the app over gPRC with automatic instance management.
@@ -47,6 +50,7 @@ func Manage(pluginID string, instanceFactory InstanceFactoryFunc, opts ManageOpt
4750
QueryDataHandler: handler,
4851
StreamHandler: handler,
4952
AdmissionHandler: opts.AdmissionHandler,
53+
ConversionHandler: opts.ConversionHandler,
5054
GRPCSettings: opts.GRPCSettings,
5155
})
5256
}

backend/conversion.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
)
6+
7+
const (
8+
// EndpointConvertObject friendly name for the convert object endpoint/handler.
9+
EndpointConvertObject Endpoint = "convertObject"
10+
)
11+
12+
// ConversionHandler is an EXPERIMENTAL service that allows converting objects between versions
13+
// This is modeled after the kubernetes CRD conversion webhooks.
14+
// Since grafana 11.1, this feature is under active development and will continue to evolve in 2024
15+
// This may also be replaced with a more native kubernetes solution that does not work with existing tooling
16+
17+
type ConversionHandler interface {
18+
// ConvertObject is called to covert objects between different versions
19+
ConvertObjects(context.Context, *ConversionRequest) (*ConversionResponse, error)
20+
}
21+
22+
type ConvertObjectsFunc func(context.Context, *ConversionRequest) (*ConversionResponse, error)
23+
24+
type GroupVersion struct {
25+
Group string `json:"group,omitempty"`
26+
Version string `json:"version,omitempty"`
27+
}
28+
29+
// ConversionRequest supports converting an object from on version to another
30+
type ConversionRequest struct {
31+
// NOTE: this may not include app or datasource instance settings depending on the request
32+
PluginContext PluginContext `json:"pluginContext,omitempty"`
33+
// UID is an identifier for the individual request/response. It allows distinguishing instances of requests which are
34+
// otherwise identical (parallel requests, etc).
35+
// The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request.
36+
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
37+
UID string `json:"uid,omitempty"`
38+
// TargetVersion is the version the object should be converted to.
39+
TargetVersion GroupVersion `json:"target_version,omitempty"`
40+
// Objects is the list of objects to convert. This contains the full metadata envelope.
41+
Objects []RawObject `json:"objects,omitempty"`
42+
}
43+
44+
type RawObject struct {
45+
// Raw is the underlying serialization of this object.
46+
Raw []byte `json:"-" `
47+
// ContentType is the media type of the object.
48+
ContentType string `json:"-"`
49+
}
50+
51+
type ConversionResponse struct {
52+
// UID is an identifier for the individual request/response.
53+
// This should be copied over from the corresponding `request.uid`.
54+
UID string `json:"uid,omitempty"`
55+
// Result contains extra details into why an admission request was denied.
56+
Result *StatusResult `json:"result,omitempty"`
57+
// Objects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty.
58+
Objects []RawObject `json:"objects,omitempty"`
59+
}

backend/conversion_adapter.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
6+
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
7+
)
8+
9+
type conversionSDKAdapter struct {
10+
handler ConversionHandler
11+
}
12+
13+
func newConversionSDKAdapter(handler ConversionHandler) *conversionSDKAdapter {
14+
return &conversionSDKAdapter{
15+
handler: handler,
16+
}
17+
}
18+
19+
func (a *conversionSDKAdapter) ConvertObjects(ctx context.Context, req *pluginv2.ConversionRequest) (*pluginv2.ConversionResponse, error) {
20+
ctx = setupContext(ctx, EndpointConvertObject)
21+
parsedReq := FromProto().ConversionRequest(req)
22+
23+
var resp *ConversionResponse
24+
err := wrapHandler(ctx, parsedReq.PluginContext, func(ctx context.Context) (RequestStatus, error) {
25+
var innerErr error
26+
resp, innerErr = a.handler.ConvertObjects(ctx, parsedReq)
27+
return RequestStatusFromError(innerErr), innerErr
28+
})
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
return ToProto().ConversionResponse(resp), nil
34+
}

backend/convert_from_protobuf.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,31 @@ func (f ConvertFromProtobuf) GroupVersionKind(req *pluginv2.GroupVersionKind) Gr
319319
}
320320
}
321321

322+
// GroupVersion ...
323+
func (f ConvertFromProtobuf) GroupVersion(req *pluginv2.GroupVersion) GroupVersion {
324+
return GroupVersion{
325+
Group: req.Group,
326+
Version: req.Version,
327+
}
328+
}
329+
330+
// RawObject ...
331+
func (f ConvertFromProtobuf) RawObject(req *pluginv2.RawObject) RawObject {
332+
return RawObject{
333+
Raw: req.Raw,
334+
ContentType: req.ContentType,
335+
}
336+
}
337+
338+
// RawObjects ...
339+
func (f ConvertFromProtobuf) RawObjects(req []*pluginv2.RawObject) []RawObject {
340+
rawObjects := make([]RawObject, len(req))
341+
for i, r := range req {
342+
rawObjects[i] = f.RawObject(r)
343+
}
344+
return rawObjects
345+
}
346+
322347
// AdmissionRequest ...
323348
func (f ConvertFromProtobuf) AdmissionRequest(req *pluginv2.AdmissionRequest) *AdmissionRequest {
324349
return &AdmissionRequest{
@@ -334,9 +359,9 @@ func (f ConvertFromProtobuf) AdmissionRequest(req *pluginv2.AdmissionRequest) *A
334359
func (f ConvertFromProtobuf) ConversionRequest(req *pluginv2.ConversionRequest) *ConversionRequest {
335360
return &ConversionRequest{
336361
PluginContext: f.PluginContext(req.PluginContext),
337-
Kind: f.GroupVersionKind(req.Kind),
338-
ObjectBytes: req.ObjectBytes,
339-
TargetVersion: req.TargetVersion,
362+
UID: req.Uid,
363+
Objects: f.RawObjects(req.Objects),
364+
TargetVersion: f.GroupVersion(req.TargetVersion),
340365
}
341366
}
342367

@@ -362,9 +387,9 @@ func (f ConvertFromProtobuf) ValidationResponse(rsp *pluginv2.ValidationResponse
362387
// ConversionResponse ...
363388
func (f ConvertFromProtobuf) ConversionResponse(rsp *pluginv2.ConversionResponse) *ConversionResponse {
364389
return &ConversionResponse{
365-
Allowed: rsp.Allowed,
366-
Result: f.StatusResult(rsp.Result),
367-
ObjectBytes: rsp.ObjectBytes,
390+
UID: rsp.Uid,
391+
Result: f.StatusResult(rsp.Result),
392+
Objects: f.RawObjects(rsp.Objects),
368393
}
369394
}
370395

0 commit comments

Comments
 (0)