| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package funcctl |
| 4 | |
| 5 | import ( |
| 6 | "encoding/json" |
| 7 | "fmt" |
| 8 | |
| 9 | "github.com/netdata/netdata/go/plugins/pkg/funcapi" |
| 10 | "github.com/netdata/netdata/go/plugins/plugin/framework/dyncfg" |
| 11 | "github.com/netdata/netdata/go/plugins/plugin/framework/functions" |
| 12 | ) |
| 13 | |
| 14 | type methodResponseWriter func(dataResp *funcapi.FunctionResponse, methodParams []funcapi.ParamConfig, updateEvery int) |
| 15 | |
| 16 | func (c *Controller) respondWithParams(fn functions.Function, moduleName string, dataResp *funcapi.FunctionResponse, methodParams []funcapi.ParamConfig, updateEvery int, methodType string, includeJobParam bool) { |
| 17 | c.respondMethodDataWithParams( |
| 18 | fn, |
| 19 | dataResp, |
| 20 | methodParams, |
| 21 | updateEvery, |
| 22 | methodType, |
| 23 | func(params []funcapi.ParamConfig) []string { |
| 24 | return buildAcceptedParams(params, includeJobParam) |
| 25 | }, |
| 26 | func(params []funcapi.ParamConfig) []map[string]any { |
| 27 | return c.buildRequiredParams(moduleName, params, includeJobParam) |
| 28 | }, |
| 29 | ) |
| 30 | } |
| 31 | |
| 32 | func (c *Controller) respondJobMethodWithParams(fn functions.Function, dataResp *funcapi.FunctionResponse, methodParams []funcapi.ParamConfig, updateEvery int, methodType string) { |
| 33 | c.respondMethodDataWithParams( |
| 34 | fn, |
| 35 | dataResp, |
| 36 | methodParams, |
| 37 | updateEvery, |
| 38 | methodType, |
| 39 | buildJobMethodAcceptedParams, |
| 40 | buildJobMethodRequiredParams, |
| 41 | ) |
| 42 | } |
| 43 | |
| 44 | func (c *Controller) respondMethodDataWithParams( |
| 45 | fn functions.Function, |
| 46 | dataResp *funcapi.FunctionResponse, |
| 47 | methodParams []funcapi.ParamConfig, |
| 48 | updateEvery int, |
| 49 | methodType string, |
| 50 | buildAccepted func([]funcapi.ParamConfig) []string, |
| 51 | buildRequired func([]funcapi.ParamConfig) []map[string]any, |
| 52 | ) { |
| 53 | if dataResp == nil { |
| 54 | c.respondError(fn, 500, "internal error: module returned nil response") |
| 55 | return |
| 56 | } |
| 57 | if dataResp.Status >= 400 { |
| 58 | c.respondError(fn, dataResp.Status, "%s", dataResp.Message) |
| 59 | return |
| 60 | } |
| 61 | |
| 62 | paramsForResponse := methodParams |
| 63 | if len(dataResp.RequiredParams) > 0 { |
| 64 | paramsForResponse = funcapi.MergeParamConfigs(paramsForResponse, dataResp.RequiredParams) |
| 65 | } |
| 66 | |
| 67 | resp := map[string]any{ |
| 68 | "v": 3, |
| 69 | "update_every": updateEvery, |
| 70 | "status": dataResp.Status, |
| 71 | "type": resolveResponseType(dataResp.ResponseType, methodType), |
| 72 | "has_history": false, |
| 73 | "help": dataResp.Help, |
| 74 | "accepted_params": buildAccepted(paramsForResponse), |
| 75 | "required_params": buildRequired(paramsForResponse), |
| 76 | } |
| 77 | |
| 78 | if dataResp.Columns != nil { |
| 79 | resp["columns"] = dataResp.Columns |
| 80 | } |
| 81 | if dataResp.Data != nil { |
| 82 | resp["data"] = dataResp.Data |
| 83 | } |
| 84 | if dataResp.DefaultSortColumn != "" { |
| 85 | resp["default_sort_column"] = dataResp.DefaultSortColumn |
| 86 | } |
| 87 | if len(dataResp.Charts) > 0 { |
| 88 | resp["charts"] = dataResp.Charts |
| 89 | } |
| 90 | if len(dataResp.DefaultCharts) > 0 { |
| 91 | resp["default_charts"] = dataResp.DefaultCharts.Build() |
| 92 | } |
| 93 | if len(dataResp.GroupBy) > 0 { |
| 94 | resp["group_by"] = dataResp.GroupBy |
| 95 | } |
| 96 | |
| 97 | c.respondJSON(fn, resp) |
| 98 | } |
| 99 | |
| 100 | func resolveResponseType(dataType, methodType string) string { |
| 101 | if dataType != "" { |
| 102 | return dataType |
| 103 | } |
| 104 | if methodType != "" { |
| 105 | return methodType |
| 106 | } |
| 107 | return "table" |
| 108 | } |
| 109 | |
| 110 | func (c *Controller) respondError(fn functions.Function, status int, format string, args ...any) { |
| 111 | c.respondJSON(fn, map[string]any{ |
| 112 | "status": status, |
| 113 | "errorMessage": fmt.Sprintf(format, args...), |
| 114 | }) |
| 115 | } |
| 116 | |
| 117 | func (c *Controller) respondJSON(fn functions.Function, resp map[string]any) { |
| 118 | data, err := json.Marshal(resp) |
| 119 | if err != nil { |
| 120 | c.Errorf("failed to marshal function response: %v", err) |
| 121 | c.sendJSON(fn, string(functions.BuildJSONPayload(500, "internal error: failed to encode response")), 500) |
| 122 | return |
| 123 | } |
| 124 | |
| 125 | code := 200 |
| 126 | if status, ok := resp["status"]; ok { |
| 127 | switch value := status.(type) { |
| 128 | case int: |
| 129 | code = value |
| 130 | case int64: |
| 131 | code = int(value) |
| 132 | case float64: |
| 133 | code = int(value) |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | c.sendJSON(fn, string(data), code) |
| 138 | } |
| 139 | |
| 140 | func (c *Controller) sendJSON(fn functions.Function, payload string, code int) { |
| 141 | if c.jsonWriter != nil { |
| 142 | c.jsonWriter([]byte(payload), code) |
| 143 | return |
| 144 | } |
| 145 | if c.api == nil { |
| 146 | return |
| 147 | } |
| 148 | |
| 149 | c.api.SendJSONWithCode(dyncfg.NewFunction(fn), payload, code) |
| 150 | } |