1package graphql
2
3import (
4	"context"
5	"errors"
6
7	"github.com/graph-gophers/dataloader"
8	"github.com/sensu/sensu-go/backend/apid/actions"
9	"github.com/sensu/sensu-go/cli/client"
10	"github.com/sensu/sensu-go/types"
11)
12
13type key int
14
15const (
16	loadersKey key = iota
17	assetsLoaderKey
18	checkConfigsLoaderKey
19	entitiesLoaderKey
20	eventsLoaderKey
21	eventFiltersLoaderKey
22	handlersLoaderKey
23	mutatorsLoaderKey
24	namespacesLoaderKey
25	silencedsLoaderKey
26)
27
28var (
29	errLoadersNotFound        = errors.New("loaders was not found inside context")
30	errLoaderNotFound         = errors.New("loader was not found")
31	errUnexpectedLoaderResult = errors.New("loader returned unexpected result")
32)
33
34// assets
35
36func loadAssetsBatchFn(c client.APIClient) dataloader.BatchFunc {
37	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
38		results := make([]*dataloader.Result, 0, len(keys))
39		for _, key := range keys {
40			records, err := c.ListAssets(key.String(), &client.ListOptions{})
41			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
42			results = append(results, result)
43		}
44		return results
45	}
46}
47
48func loadAssets(ctx context.Context, ns string) ([]types.Asset, error) {
49	var records []types.Asset
50	loader, err := getLoader(ctx, assetsLoaderKey)
51	if err != nil {
52		return records, err
53	}
54
55	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
56	records, ok := results.([]types.Asset)
57	if err == nil && !ok {
58		err = errUnexpectedLoaderResult
59	}
60	return records, err
61}
62
63// checks
64
65func loadCheckConfigsBatchFn(c client.APIClient) dataloader.BatchFunc {
66	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
67		results := make([]*dataloader.Result, 0, len(keys))
68		for _, key := range keys {
69			records, err := c.ListChecks(key.String(), &client.ListOptions{})
70			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
71			results = append(results, result)
72		}
73		return results
74	}
75}
76
77func loadCheckConfigs(ctx context.Context, ns string) ([]types.CheckConfig, error) {
78	var records []types.CheckConfig
79	loader, err := getLoader(ctx, checkConfigsLoaderKey)
80	if err != nil {
81		return records, err
82	}
83
84	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
85	records, ok := results.([]types.CheckConfig)
86	if err == nil && !ok {
87		err = errUnexpectedLoaderResult
88	}
89	return records, err
90}
91
92// entities
93
94func loadEntitiesBatchFn(c client.APIClient) dataloader.BatchFunc {
95	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
96		results := make([]*dataloader.Result, 0, len(keys))
97		for _, key := range keys {
98			records, err := c.ListEntities(key.String(), &client.ListOptions{})
99			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
100			results = append(results, result)
101		}
102		return results
103	}
104}
105
106func loadEntities(ctx context.Context, ns string) ([]types.Entity, error) {
107	var records []types.Entity
108	loader, err := getLoader(ctx, entitiesLoaderKey)
109	if err != nil {
110		return records, err
111	}
112
113	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
114	records, ok := results.([]types.Entity)
115	if err == nil && !ok {
116		err = errUnexpectedLoaderResult
117	}
118	return records, err
119}
120
121// events
122
123func loadEventsBatchFn(c client.APIClient) dataloader.BatchFunc {
124	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
125		results := make([]*dataloader.Result, 0, len(keys))
126		for _, key := range keys {
127			records, err := c.ListEvents(key.String(), &client.ListOptions{})
128			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
129			results = append(results, result)
130		}
131		return results
132	}
133}
134
135func loadEvents(ctx context.Context, ns string) ([]types.Event, error) {
136	var records []types.Event
137	loader, err := getLoader(ctx, eventsLoaderKey)
138	if err != nil {
139		return records, err
140	}
141
142	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
143	records, ok := results.([]types.Event)
144	if err == nil && !ok {
145		err = errUnexpectedLoaderResult
146	}
147	return records, err
148}
149
150// event filters
151
152func loadEventFiltersBatchFn(c client.APIClient) dataloader.BatchFunc {
153	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
154		results := make([]*dataloader.Result, 0, len(keys))
155		for _, key := range keys {
156			records, err := c.ListFilters(key.String(), &client.ListOptions{})
157			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
158			results = append(results, result)
159		}
160		return results
161	}
162}
163
164func loadEventFilters(ctx context.Context, ns string) ([]types.EventFilter, error) {
165	var records []types.EventFilter
166	loader, err := getLoader(ctx, eventFiltersLoaderKey)
167	if err != nil {
168		return records, err
169	}
170
171	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
172	records, ok := results.([]types.EventFilter)
173	if err == nil && !ok {
174		err = errUnexpectedLoaderResult
175	}
176	return records, err
177}
178
179// handlers
180
181func loadHandlersBatchFn(c client.APIClient) dataloader.BatchFunc {
182	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
183		results := make([]*dataloader.Result, 0, len(keys))
184		for _, key := range keys {
185			records, err := c.ListHandlers(key.String(), &client.ListOptions{})
186			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
187			results = append(results, result)
188		}
189		return results
190	}
191}
192
193func loadHandlers(ctx context.Context, ns string) ([]types.Handler, error) {
194	var records []types.Handler
195	loader, err := getLoader(ctx, handlersLoaderKey)
196	if err != nil {
197		return records, err
198	}
199
200	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
201	records, ok := results.([]types.Handler)
202	if err == nil && !ok {
203		err = errUnexpectedLoaderResult
204	}
205	return records, err
206}
207
208// mutators
209
210func loadMutatorsBatchFn(c client.APIClient) dataloader.BatchFunc {
211	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
212		results := make([]*dataloader.Result, 0, len(keys))
213		for _, key := range keys {
214			records, err := c.ListMutators(key.String(), &client.ListOptions{})
215			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
216			results = append(results, result)
217		}
218		return results
219	}
220}
221
222func loadMutators(ctx context.Context, ns string) ([]types.Mutator, error) {
223	var records []types.Mutator
224	loader, err := getLoader(ctx, mutatorsLoaderKey)
225	if err != nil {
226		return records, err
227	}
228
229	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
230	records, ok := results.([]types.Mutator)
231	if err == nil && !ok {
232		err = errUnexpectedLoaderResult
233	}
234	return records, err
235}
236
237// namespaces
238
239func loadNamespacesBatchFn(c client.APIClient) dataloader.BatchFunc {
240	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
241		results := make([]*dataloader.Result, 0, len(keys))
242		for range keys {
243			records, err := c.ListNamespaces(&client.ListOptions{})
244			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
245			results = append(results, result)
246		}
247		return results
248	}
249}
250
251func loadNamespaces(ctx context.Context) ([]types.Namespace, error) {
252	var records []types.Namespace
253	loader, err := getLoader(ctx, namespacesLoaderKey)
254	if err != nil {
255		return records, err
256	}
257
258	results, err := loader.Load(ctx, dataloader.StringKey("*"))()
259	records, ok := results.([]types.Namespace)
260	if err == nil && !ok {
261		err = errUnexpectedLoaderResult
262	}
263	return records, err
264}
265
266// silences
267
268func loadSilencedsBatchFn(c client.APIClient) dataloader.BatchFunc {
269	return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
270		results := make([]*dataloader.Result, 0, len(keys))
271		for _, key := range keys {
272			records, err := c.ListSilenceds(key.String(), "", "", &client.ListOptions{})
273			result := &dataloader.Result{Data: records, Error: handleListErr(err)}
274			results = append(results, result)
275		}
276		return results
277	}
278}
279
280func loadSilenceds(ctx context.Context, ns string) ([]types.Silenced, error) {
281	var records []types.Silenced
282	loader, err := getLoader(ctx, silencedsLoaderKey)
283	if err != nil {
284		return records, err
285	}
286
287	results, err := loader.Load(ctx, dataloader.StringKey(ns))()
288	records, ok := results.([]types.Silenced)
289	if err == nil && !ok {
290		err = errUnexpectedLoaderResult
291	}
292	return records, err
293}
294
295func contextWithLoaders(ctx context.Context, client client.APIClient, opts ...dataloader.Option) context.Context {
296	// Currently all fields are resolved serially, as such we disable batching and
297	// rely only on dataloader's cache.
298	opts = append([]dataloader.Option{dataloader.WithBatchCapacity(1)}, opts...)
299
300	loaders := map[key]*dataloader.Loader{}
301	loaders[assetsLoaderKey] = dataloader.NewBatchedLoader(loadAssetsBatchFn(client), opts...)
302	loaders[checkConfigsLoaderKey] = dataloader.NewBatchedLoader(loadCheckConfigsBatchFn(client), opts...)
303	loaders[entitiesLoaderKey] = dataloader.NewBatchedLoader(loadEntitiesBatchFn(client), opts...)
304	loaders[eventsLoaderKey] = dataloader.NewBatchedLoader(loadEventsBatchFn(client), opts...)
305	loaders[eventFiltersLoaderKey] = dataloader.NewBatchedLoader(loadEventFiltersBatchFn(client), opts...)
306	loaders[handlersLoaderKey] = dataloader.NewBatchedLoader(loadHandlersBatchFn(client), opts...)
307	loaders[mutatorsLoaderKey] = dataloader.NewBatchedLoader(loadMutatorsBatchFn(client), opts...)
308	loaders[namespacesLoaderKey] = dataloader.NewBatchedLoader(loadNamespacesBatchFn(client), opts...)
309	loaders[silencedsLoaderKey] = dataloader.NewBatchedLoader(loadSilencedsBatchFn(client), opts...)
310	return context.WithValue(ctx, loadersKey, loaders)
311}
312
313func getLoader(ctx context.Context, loaderKey key) (*dataloader.Loader, error) {
314	loaders, ok := ctx.Value(loadersKey).(map[key]*dataloader.Loader)
315	if !ok {
316		return nil, errLoadersNotFound
317	}
318
319	loader, ok := loaders[loaderKey]
320	if !ok {
321		return loader, errLoaderNotFound
322	}
323	return loader, nil
324}
325
326// When resolving a field, GraphQL does not consider the absence of a value an
327// error; as such we omit the error if the API client returns Permission denied.
328func handleListErr(err error) error {
329	if apiErr, ok := err.(client.APIError); ok {
330		if apiErr.Code == uint32(actions.PermissionDenied) {
331			return nil
332		}
333	}
334	return err
335}
336
337// When resolving a field, GraphQL does not consider the absence of a value an
338// error; as such we omit the error when the API client returns NotFound or
339// Permission denied.
340func handleFetchResult(resource interface{}, err error) (interface{}, error) {
341	if apiErr, ok := err.(client.APIError); ok {
342		if apiErr.Code == uint32(actions.NotFound) || apiErr.Code == uint32(actions.PermissionDenied) {
343			return nil, nil
344		}
345	}
346	if err != nil {
347		return nil, err
348	}
349	return resource, err
350}
351