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