-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathmain.go
More file actions
162 lines (143 loc) · 5.84 KB
/
main.go
File metadata and controls
162 lines (143 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package main
import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"github.com/grafana/grafana-app-sdk/apiserver"
corev1 "github.com/grafana/grafana-app-sdk/examples/apiserver/apis/core/v1"
corev2 "github.com/grafana/grafana-app-sdk/examples/apiserver/apis/core/v2"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/component-base/cli"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)
func main() {
// For each resource we want to serve from the API server, we need to create an apiserver.Resource
// An apiserver.Resource MUST contain a Kind and GetOpenAPIDefinitions, both of which are generated by the codegen.
// Optionally, an apiserver.Resource can contain additional Subresources that have HTTP handlers,
// Admission control validation and mutation logic, and a Reconciler to watch events for the Kind.
// Create an API Server Resource for the v1 ExternalName
externalNameV1 := apiserver.Resource{
Kind: corev1.ExternalNameKind(),
GetOpenAPIDefinitions: corev1.GetOpenAPIDefinitions,
// Example "foo" subresource that just prints out some JSON payload
Subresources: []apiserver.SubresourceRoute{{
Path: "foo",
Handler: handleFooSubresource,
// FIXME: Currently this is not registered with the APIServer in any way
OpenAPISpec: fooSubresourceOpenAPI,
}},
Mutator: &mutatingAdmissionController{},
Validator: &validatingAdmissionController{},
// Reconciler to run for this kind
Reconciler: &simple.Reconciler{
ReconcileFunc: reconcileV1ExternalNames,
},
}
// Create an API Server Resource for the v2 ExternalName
externalNameV2 := apiserver.Resource{
Kind: corev2.ExternalNameKind(),
GetOpenAPIDefinitions: corev2.GetOpenAPIDefinitions,
// No Reconciler for this one, because we'd get duplicate events (one each for v1 and v2 of the kind)
}
// apiserver.Resource items must be added to an apiserver.ResourceGroup.
// Currently, there is no validation that the Name in the ResourceGroup matches the Group in each added Resource
// TODO: have a Validate() method on ResourceGroup to check that?
resourceGroup := apiserver.NewResourceGroup(corev1.ExternalNameKind().Group(), []apiserver.Resource{externalNameV1, externalNameV2})
// APIServerOptions is used to create the API server from one or more ResourceGroups.
// TODO: this will be expanded upon
o := simple.NewAPIServerOptions([]apiserver.ResourceGroup{*resourceGroup}, os.Stdout, os.Stderr)
o.RecommendedOptions.Authorization = nil
o.RecommendedOptions.Authentication = nil
o.RecommendedOptions.CoreAPI = nil
ch := make(chan struct{})
cmd := simple.NewCommandStartAPIServer(o, ch)
code := cli.Run(cmd)
os.Exit(code)
}
var _ resource.MutatingAdmissionController = &mutatingAdmissionController{}
type mutatingAdmissionController struct{}
func (m *mutatingAdmissionController) Mutate(ctx context.Context, request *resource.AdmissionRequest) (*resource.MutatingResponse, error) {
obj := request.Object
obj.SetAnnotations(map[string]string{"mutated": "true"})
return &resource.MutatingResponse{
UpdatedObject: obj,
}, nil
}
var _ resource.ValidatingAdmissionController = &validatingAdmissionController{}
type validatingAdmissionController struct{}
func (v *validatingAdmissionController) Validate(ctx context.Context, request *resource.AdmissionRequest) error {
if request.Object.GetName() == "invalid" {
return k8s.NewAdmissionError(fmt.Errorf("object name cannot be 'invalid'"), http.StatusBadRequest, "invalid name")
}
return nil
}
func reconcileV1ExternalNames(ctx context.Context, request operator.ReconcileRequest) (operator.ReconcileResult, error) {
fmt.Printf("Received %s event for %s\n", operator.ResourceActionFromReconcileAction(request.Action), request.Object.GetName())
cast, ok := request.Object.(*corev1.ExternalName)
if !ok {
return operator.ReconcileResult{}, fmt.Errorf("reconcile object is not *corev1.ExternalName (gvk=%s)", request.Object.GroupVersionKind().String())
}
fmt.Printf("ResourceVersion: %s, Host: %s\n", cast.GetResourceVersion(), cast.Spec.Host)
return operator.ReconcileResult{}, nil
}
func handleFooSubresource(w http.ResponseWriter, r *http.Request, identifier resource.Identifier) {
fmt.Println("Called foo subresource for externalName: ", identifier)
w.Write([]byte(`{"notright":2}`))
}
func fooSubresourceOpenAPI(callback common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana-app-sdk/examples/apiserver/apis/core/v1.ExternalNameFoo": common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ExternalNameFoo defines model for ExternalNameFoo.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"foo": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"foo"},
},
},
},
}
}
type Converter struct {
}
func (c *Converter) Convert(obj k8s.RawKind, targetAPIVersion string) ([]byte, error) {
fmt.Println("Convert " + obj.APIVersion + " to " + targetAPIVersion)
var target resource.Object
ver := obj.Version
switch targetAPIVersion {
case "core.grafana.internal/v1":
target = &corev1.ExternalName{}
ver = "v1"
case "core.grafana.internal/v2":
target = &corev2.ExternalName{}
ver = "v2"
}
codec := corev1.ExternalNameJSONCodec{}
err := codec.Read(bytes.NewReader(obj.Raw), target)
if err != nil {
return nil, err
}
target.SetGroupVersionKind(schema.GroupVersionKind{
Kind: obj.Kind,
Group: obj.Group,
Version: ver,
})
buf := bytes.Buffer{}
err = codec.Write(&buf, target)
return buf.Bytes(), err
}