forked from kubernetes-sigs/apiserver-runtime
/
builder_resource.go
214 lines (188 loc) · 9.08 KB
/
builder_resource.go
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
"strings"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/runtime/schema"
regsitryrest "k8s.io/apiserver/pkg/registry/rest"
"github.com/zachaller/apiserver-runtime/internal/sample-apiserver/pkg/apiserver"
"github.com/zachaller/apiserver-runtime/pkg/builder/resource"
"github.com/zachaller/apiserver-runtime/pkg/builder/resource/resourcerest"
"github.com/zachaller/apiserver-runtime/pkg/builder/rest"
)
// WithResource registers the resource with the apiserver.
//
// If no versions of this GroupResource have already been registered, a new default handler will be registered.
// If the object implements rest.Getter, rest.Updater or rest.Creator then the provided object itself will be
// used as the rest handler for the resource type.
//
// If no versions of this GroupResource have already been registered and the object does NOT implement the rest
// interfaces, then a new etcd backed storage will be created for the object and used as the handler.
// The storage will use a DefaultStrategy, which delegates functions to the object if the object implements
// interfaces defined in the "apiserver-runtime/pkg/builder/rest" package. Otherwise it will provide a default
// behavior.
//
// WithResource will automatically register the "status" subresource if the object implements the
// resource.StatusGetSetter interface.
//
// WithResource will automatically register version-specific defaulting for this GroupVersionResource
// if the object implements the resource.Defaulter interface.
//
// WithResource automatically adds the object and its list type to the known types. If the object also declares itself
// as the storage version, the object and its list type will be added as storage versions to the SchemeBuilder as well.
// The storage version is the version accepted by the handler.
//
// If another version of the object's GroupResource has already been registered, then the resource will use the
// handler already registered for that version of the GroupResource. Objects for this version will be converted
// to the object version which the handler accepts before the handler is invoked.
func (a *Server) WithResource(obj resource.Object) *Server {
gvr := obj.GetGroupVersionResource()
a.schemeBuilder.Register(resource.AddToScheme(obj))
// reuse the storage if this resource has already been registered
if s, found := a.storageProvider[gvr.GroupResource()]; found {
_ = a.forGroupVersionResource(gvr, s.Get)
return a
}
var parentStorageProvider rest.ResourceHandlerProvider
defer func() {
// automatically create status subresource if the object implements the status interface
a.withSubResourceIfExists(obj, parentStorageProvider)
}()
// If the type implements it's own storage, then use that
switch s := obj.(type) {
case resourcerest.Creator, resourcerest.Updater, resourcerest.Getter, resourcerest.Lister:
parentStorageProvider = rest.StaticHandlerProvider{Storage: s.(regsitryrest.Storage)}.Get
default:
parentStorageProvider = rest.New(obj)
}
_ = a.forGroupVersionResource(gvr, parentStorageProvider)
return a
}
// WithResourceAndStrategy registers the resource with the apiserver creating a new etcd backed storage
// for the GroupResource using the provided strategy. In most cases callers should instead use WithResource
// and implement the interfaces defined in "apiserver-runtime/pkg/builder/rest" to control the Strategy.
//
// Note: WithResourceAndHandler should never be called after the GroupResource has already been registered with
// another version.
func (a *Server) WithResourceAndStrategy(obj resource.Object, strategy rest.Strategy) *Server {
gvr := obj.GetGroupVersionResource()
a.schemeBuilder.Register(resource.AddToScheme(obj))
parentStorageProvider := rest.NewWithStrategy(obj, strategy)
_ = a.forGroupVersionResource(gvr, parentStorageProvider)
// automatically create status subresource if the object implements the status interface
defer func() {
// automatically create status subresource if the object implements the status interface
a.withSubResourceIfExists(obj, parentStorageProvider)
}()
return a
}
// WithResourceAndHandler registers a request handler for the resource rather than the default
// etcd backed storage.
//
// Note: WithResourceAndHandler should never be called after the GroupResource has already been registered with
// another version.
//
// Note: WithResourceAndHandler will NOT register the "status" subresource for the resource object.
func (a *Server) WithResourceAndHandler(obj resource.Object, sp rest.ResourceHandlerProvider) *Server {
gvr := obj.GetGroupVersionResource()
a.schemeBuilder.Register(resource.AddToScheme(obj))
defer func() {
// automatically create status subresource if the object implements the status interface
a.withSubResourceIfExists(obj, sp)
}()
return a.forGroupVersionResource(gvr, sp)
}
// WithResourceAndStorage registers the resource with the apiserver, applying fn to the storage for the resource
// before completing it.
//
// May be used to change low-level storage configuration or swap out the storage backend to something other than
// etcd.
//
// Note: WithResourceAndHandler should never be called after the GroupResource has already been registered with
// another version.
func (a *Server) WithResourceAndStorage(obj resource.Object, fn rest.StoreFn) *Server {
gvr := obj.GetGroupVersionResource()
a.schemeBuilder.Register(resource.AddToScheme(obj))
sp := rest.NewWithFn(obj, fn)
defer func() {
// automatically create status subresource if the object implements the status interface
a.withSubResourceIfExists(obj, sp)
}()
return a.forGroupVersionResource(gvr, sp)
}
// forGroupVersionResource manually registers storage for a specific resource.
func (a *Server) forGroupVersionResource(
gvr schema.GroupVersionResource, sp rest.ResourceHandlerProvider) *Server {
// register the group version
a.withGroupVersions(gvr.GroupVersion())
// TODO: make sure folks don't register multiple storageProvider instance for the same group-resource
// don't replace the existing instance otherwise it will chain wrapped singletonProviders when
// fetching from the map before calling this function
if _, found := a.storageProvider[gvr.GroupResource()]; !found {
a.storageProvider[gvr.GroupResource()] = &singletonProvider{Provider: sp}
}
// add the API with its storageProvider
apiserver.APIs[gvr] = sp
return a
}
// forGroupVersionSubResource manually registers storageProvider for a specific subresource.
func (a *Server) forGroupVersionSubResource(
gvr schema.GroupVersionResource, parentProvider rest.ResourceHandlerProvider, subResourceProvider rest.ResourceHandlerProvider) {
isSubResource := strings.Contains(gvr.Resource, "/")
if !isSubResource {
klog.Fatalf("Expected status subresource but received %v/%v/%v", gvr.Group, gvr.Version, gvr.Resource)
}
// add the API with its storageProvider for subresource
apiserver.APIs[gvr] = (&subResourceStorageProvider{
subResourceGVR: gvr,
parentStorageProvider: parentProvider,
subResourceStorageProvider: subResourceProvider,
}).Get
}
// WithSchemeInstallers registers functions to install resource types into the Scheme.
func (a *Server) withGroupVersions(versions ...schema.GroupVersion) *Server {
if a.groupVersions == nil {
a.groupVersions = map[schema.GroupVersion]bool{}
}
for _, gv := range versions {
if _, found := a.groupVersions[gv]; found {
continue
}
a.groupVersions[gv] = true
a.orderedGroupVersions = append(a.orderedGroupVersions, gv)
}
return a
}
func (a *Server) withSubResourceIfExists(obj resource.Object, parentStorageProvider rest.ResourceHandlerProvider) {
parentGVR := obj.GetGroupVersionResource()
// automatically create status subresource if the object implements the status interface
if _, ok := obj.(resource.ObjectWithStatusSubResource); ok {
statusGVR := parentGVR.GroupVersion().WithResource(parentGVR.Resource + "/status")
a.forGroupVersionSubResource(statusGVR, parentStorageProvider, nil)
}
if _, ok := obj.(resource.ObjectWithScaleSubResource); ok {
subResourceGVR := parentGVR.GroupVersion().WithResource(parentGVR.Resource + "/scale")
a.forGroupVersionSubResource(subResourceGVR, parentStorageProvider, nil)
}
if sgs, ok := obj.(resource.ObjectWithArbitrarySubResource); ok {
for _, sub := range sgs.GetArbitrarySubResources() {
sub := sub
subResourceGVR := parentGVR.GroupVersion().WithResource(parentGVR.Resource + "/" + sub.SubResourceName())
a.forGroupVersionSubResource(subResourceGVR, parentStorageProvider, rest.ParentStaticHandlerProvider{
Storage: sub,
ParentProvider: parentStorageProvider,
}.Get)
}
}
}