-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathstore.go
More file actions
265 lines (233 loc) · 7.99 KB
/
store.go
File metadata and controls
265 lines (233 loc) · 7.99 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
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"time"
"k8s.io/client-go/tools/clientcmd"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/resource"
)
// Schemas are defined here for clarity
var (
obj1Kind = resource.Kind{
Schema: resource.NewSimpleSchema("org.example.obj", "v1", &Obj1{}, &resource.UntypedList{}),
Codecs: map[resource.KindEncoding]resource.Codec{resource.KindEncodingJSON: resource.NewJSONCodec()},
}
obj2Kind = resource.Kind{
Schema: resource.NewSimpleSchema("org.example.obj", "v1", &Obj2{}, &resource.UntypedList{}),
Codecs: map[resource.KindEncoding]resource.Codec{resource.KindEncodingJSON: resource.NewJSONCodec()},
}
def1Kind = resource.Kind{
Schema: resource.NewSimpleSchema("org.example.def", "v1", &Def1{}, &resource.UntypedList{}),
Codecs: map[resource.KindEncoding]resource.Codec{resource.KindEncodingJSON: resource.NewJSONCodec()},
}
group1 = &group{[]resource.Kind{obj1Kind, obj2Kind}}
group2 = &group{[]resource.Kind{def1Kind}}
allSchemas = []resource.Schema{
obj1Kind,
obj2Kind,
def1Kind,
}
)
type group struct {
kinds []resource.Kind
}
func (g *group) Kinds() []resource.Kind {
return g.kinds
}
func main() {
// We're going to use kubernetes for our storage system, so we need to get a kubernetes config
// In this example, we load a kube config from a kube config file on-disk at the path specified by the kubecfg flag
kubeCfgFile := flag.String("kubecfg", "", "kube config path")
flag.Parse()
if kubeCfgFile == nil || *kubeCfgFile == "" {
_, _ = fmt.Println("--kubecfg must be set to the path of your kubernetes config file")
os.Exit(1)
}
kubeConfig, err := clientcmd.BuildConfigFromFlags("", *kubeCfgFile)
if err != nil {
panic(err)
}
kubeConfig.APIPath = "/apis" // Don't know why this isn't set correctly by default, but it isn't
// Now, let's make our Manager and ClientRegistry from our kube config.
// After this, we don't have to think about kubernetes anymore
clientGenerator := k8s.NewClientRegistry(*kubeConfig, k8s.ClientConfig{})
manager, err := k8s.NewManager(*kubeConfig)
if err != nil {
panic(err)
}
// Register all our schemas with the Manager, to ensure they exist in our storage layer
// This can also be done outside of the application with source-controlled schema
// (for example, Custom Resource Definitions in kubernetes) files generated by codegen
for _, schema := range allSchemas {
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*5)
defer cancel() //nolint:revive
err = manager.RegisterSchema(ctx, schema, resource.RegisterSchemaOptions{
NoErrorOnConflict: true, // If the schema already exists, don't throw an error, just return
WaitForAvailability: true, // Wait for the schema to be available in the system (creates can be async), or until the context is canceled
// We could also call manager.WaitForAvailability, but this just adds a convenience layer
})
if err != nil {
panic(fmt.Errorf("error registering schema '%s': %w", schema.Kind(), err))
}
}
// Do some stuff using resource.Store:
useStore(clientGenerator)
// Do some stuff using resource.SimpleStore:
useSimpleStore(clientGenerator)
}
// useStore creates a resource.Store and manipulates resources using it.
// When using a resource.Store, Schemas must be registered with the store, either by providing a SchemaGroup
// in the resource.NewStore method, or with later resource.Store.Register() calls.
// Once a Schema is registered in the store, it is referenced by its Kind() value in Store calls.
func useStore(generator resource.ClientGenerator) {
// Store usage for all managed resources
store := resource.NewStore(generator, group1, group2)
// Create an example of each of our CRD types
obj1 := Obj1{}
obj1.SetNamespace("default")
obj1.SetName("example-1")
obj1.Kind = obj1Kind.Kind()
obj1Added, err := store.Add(context.TODO(), &obj1)
if err != nil {
panic(fmt.Errorf("error adding obj1: %s", err))
}
logObj("Added Obj1 using Store", obj1Added)
obj2 := Obj2{}
obj2.Spec = Obj2Spec{
Simple1: "foo",
Simple2: "bar",
}
obj2.Namespace = "default"
obj2.Name = "example-1" // We can use the same name, because it's a different resource kind
obj2.Kind = obj1Kind.Kind()
obj2Added, err := store.Add(context.TODO(), &obj2)
if err != nil {
panic(err)
}
logObj("Added Obj2 using Store", obj2Added)
statusUpdate := Obj2{}
statusUpdate.Status.State = "Updated"
obj2Updated, _ := store.UpdateSubresource(context.TODO(), obj2Kind.Kind(), resource.Identifier{
Namespace: "default",
Name: "example-1",
}, resource.SubresourceStatus, &statusUpdate)
logObj("Updated Obj2 using Store", obj2Updated)
def1 := Def1{}
def1.Spec.Param1 = "param"
def1Added, err := store.SimpleAdd(context.TODO(), def1Kind.Kind(), resource.Identifier{
Namespace: "default",
Name: "example-1",
}, &def1)
if err != nil {
panic(err)
}
logObj("Added Def1 using Store", def1Added)
// Delete everything we made so far
err = store.Delete(context.TODO(), obj1Kind.Kind(), resource.Identifier{
Namespace: "default",
Name: "example-1",
})
if err != nil {
panic(err)
}
err = store.Delete(context.TODO(), obj2Kind.Kind(), resource.Identifier{
Namespace: "default",
Name: "example-1",
})
if err != nil {
panic(err)
}
err = store.Delete(context.TODO(), def1Kind.Kind(), resource.Identifier{
Namespace: "default",
Name: "example-1",
})
if err != nil {
panic(err)
}
}
// useSimpleStore uses a resource.SimpleStore to manipulate a resources for a single Schema.
// A resource.SimpleStore is created for one specific Schema, and has a type parameter of the Schema.ZeroValue()'s
// Object's Spec. This is intended to make it easier to work directly with the Spec objects themselves,
// rather than dealing with the extra associated data and doing type conversions.
// The trade-off is that the SimpleStore can only be tied to one specific Schema.
func useSimpleStore(generator resource.ClientGenerator) {
// SimpleStore can only manipulate one Custom Resource type, but allows for direct manipulation of the Spec object
simpleStore, _ := resource.NewSimpleStore[Obj2Spec](obj2Kind, generator) //nolint:staticcheck
added, err := simpleStore.Add(context.TODO(), resource.Identifier{
Namespace: "default",
Name: "example-2",
}, Obj2Spec{})
if err != nil {
panic(err)
}
logObj("Added using SimpleStore", added)
added.Spec.Simple2 = "updated value"
updated, err := simpleStore.Update(context.TODO(), resource.Identifier{
Namespace: "default",
Name: "example-2",
}, added.Spec)
if err != nil {
panic(err)
}
logObj("Updated using SimpleStore", updated)
// Update the status subresource
_, _ = simpleStore.UpdateSubresource(context.TODO(), resource.Identifier{
Namespace: "default",
Name: "example-2",
}, resource.SubresourceStatus, Obj2Status{
State: "foo",
})
_ = simpleStore.Delete(context.TODO(), resource.Identifier{
Namespace: "default",
Name: "example-2",
})
}
func logObj(msg string, obj any) {
j, _ := json.Marshal(obj)
log.Printf("\u001B[1;32m%s:\u001B[0m %s", msg, string(j))
}
/*
* OBJECT STRUCTS
* Typically, these are generated. Here, to keep this example self-contained, we define them
* TODO: Maybe generate these and commit generated code?
* TODO: Will need codegen to work with subresources
*
*/
type Obj1 struct {
resource.TypedSpecObject[Obj1Spec]
}
func (o1 *Obj1) Copy() resource.Object {
return resource.CopyObject(o1)
}
type Obj1Spec struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
type Obj2 struct {
resource.TypedSpecStatusObject[Obj2Spec, Obj2Status]
}
func (o2 *Obj2) Copy() resource.Object {
return resource.CopyObject(o2)
}
type Obj2Spec struct {
Simple1 string `json:"simple1"`
Simple2 string `json:"simple2"`
}
type Obj2Status struct {
State string `json:"state"`
}
type Def1 struct {
resource.TypedSpecObject[Def1Spec]
}
func (d *Def1) Copy() resource.Object {
return resource.CopyObject(d)
}
type Def1Spec struct {
Param1 string `json:"param1"`
Param2 bool `json:"param2"`
}