@cryptotaxi247 / netdata-1 / commits / 81cb35dcc

DYNCFG support deleting orphan configurations (#17023)

* dyncfg: allow deleting orphan configs * restore applied text * make the dyncfg applied text more descriptive

Costa Tsaousis committed Feb 16, 2024 at 16:39 UTC 81cb35dccacb42879c583a4ddd848d087787f6ab
2 files changed +73 -28
src/daemon/config/dyncfg-intercept.c
+40 -25
@@ -225,38 +225,44 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
225 char *cmd_str = get_word(words, num_words, i++);
226
227 if(!config || !*config || strcmp(config, PLUGINSD_FUNCTION_CONFIG) != 0)
228 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
229 - "dyncfg functions intercept: this is not a dyncfg request");
228 + return dyncfg_intercept_early_error(
229 + rfe, HTTP_RESP_BAD_REQUEST,
230 + "dyncfg functions intercept: this is not a dyncfg request");
231
232 cmd = dyncfg_cmds2id(cmd_str);
233 if(cmd == DYNCFG_CMD_NONE)
233 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
234 - "dyncfg functions intercept: invalid command received");
234 + return dyncfg_intercept_early_error(
235 + rfe, HTTP_RESP_BAD_REQUEST,
236 + "dyncfg functions intercept: invalid command received");
237
238 if(cmd == DYNCFG_CMD_ADD) {
239 add_name = get_word(words, num_words, i++);
240
241 if(!add_name || !*add_name)
240 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
241 - "dyncfg functions intercept: this action requires a name");
242 + return dyncfg_intercept_early_error(
243 + rfe, HTTP_RESP_BAD_REQUEST,
244 + "dyncfg functions intercept: this action requires a name");
245
246 if(!called_from_dyncfg_echo) {
247 char nid[strlen(id) + strlen(add_name) + 2];
248 snprintfz(nid, sizeof(nid), "%s:%s", id, add_name);
249
250 if (dictionary_get(dyncfg_globals.nodes, nid))
248 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
249 - "dyncfg functions intercept: a configuration with this name already exists");
251 + return dyncfg_intercept_early_error(
252 + rfe, HTTP_RESP_BAD_REQUEST,
253 + "dyncfg functions intercept: a configuration with this name already exists");
254 }
255 }
256
257 if((cmd == DYNCFG_CMD_ADD || cmd == DYNCFG_CMD_UPDATE || cmd == DYNCFG_CMD_TEST) && !has_payload)
254 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
255 - "dyncfg functions intercept: this action requires a payload");
258 + return dyncfg_intercept_early_error(
259 + rfe, HTTP_RESP_BAD_REQUEST,
260 + "dyncfg functions intercept: this action requires a payload");
261
262 if((cmd != DYNCFG_CMD_ADD && cmd != DYNCFG_CMD_UPDATE && cmd != DYNCFG_CMD_TEST) && has_payload)
258 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
259 - "dyncfg functions intercept: this action does not require a payload");
263 + return dyncfg_intercept_early_error(
264 + rfe, HTTP_RESP_BAD_REQUEST,
265 + "dyncfg functions intercept: this action does not require a payload");
266
267 item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
268 if(!item) {
@@ -266,7 +272,9 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
272 }
273
274 if(!item)
269 - return dyncfg_intercept_early_error(rfe, HTTP_RESP_NOT_FOUND, "dyncfg functions intercept: id is not found");
275 + return dyncfg_intercept_early_error(
276 + rfe, HTTP_RESP_NOT_FOUND,
277 + "dyncfg functions intercept: id is not found");
278 }
279
280 DYNCFG *df = dictionary_acquired_item_value(item);
@@ -278,8 +286,9 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
286 case DYNCFG_CMD_SCHEMA:
287 if(!http_access_user_has_enough_access_level_for_endpoint(rfe->user_access, df->view_access)) {
288 make_the_call_to_plugin = false;
281 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_FORBIDDEN,
282 - "dyncfg: you don't have enough view permissions to execute this command");
289 + rc = dyncfg_default_response(
290 + rfe->result.wb, HTTP_RESP_FORBIDDEN,
291 + "dyncfg: you don't have enough view permissions to execute this command");
292 }
293 break;
294
@@ -292,15 +301,17 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
301 case DYNCFG_CMD_RESTART:
302 if(!http_access_user_has_enough_access_level_for_endpoint(rfe->user_access, df->edit_access)) {
303 make_the_call_to_plugin = false;
295 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_FORBIDDEN,
296 - "dyncfg: you don't have enough edit permissions to execute this command");
304 + rc = dyncfg_default_response(
305 + rfe->result.wb, HTTP_RESP_FORBIDDEN,
306 + "dyncfg: you don't have enough edit permissions to execute this command");
307 }
308 break;
309
310 default: {
311 make_the_call_to_plugin = false;
302 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_INTERNAL_SERVER_ERROR,
303 - "dyncfg: permissions for this command are not set");
312 + rc = dyncfg_default_response(
313 + rfe->result.wb, HTTP_RESP_INTERNAL_SERVER_ERROR,
314 + "dyncfg: permissions for this command are not set");
315 }
316 break;
317 }
@@ -320,8 +331,9 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
331 else if (cmd == DYNCFG_CMD_ADD) {
332 if (df->type != DYNCFG_TYPE_TEMPLATE) {
333 make_the_call_to_plugin = false;
323 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST,
324 - "dyncfg functions intercept: add command is only allowed in templates");
334 + rc = dyncfg_default_response(
335 + rfe->result.wb, HTTP_RESP_BAD_REQUEST,
336 + "dyncfg functions intercept: add command is only allowed in templates");
337
338 nd_log(NDLS_DAEMON, NDLP_ERR,
339 "DYNCFG: add command can only be applied on templates, not %s: %s",
@@ -331,11 +343,14 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
343 else if (
344 cmd == DYNCFG_CMD_ENABLE && df->type == DYNCFG_TYPE_JOB &&
345 dyncfg_is_user_disabled(string2str(df->template))) {
334 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot enable a job of a disabled template: %s", rfe->function);
346 + nd_log(NDLS_DAEMON, NDLP_ERR,
347 + "DYNCFG: cannot enable a job of a disabled template: %s",
348 + rfe->function);
349
350 make_the_call_to_plugin = false;
337 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST,
338 - "dyncfg functions intercept: this job belongs to disabled template");
351 + rc = dyncfg_default_response(
352 + rfe->result.wb, HTTP_RESP_BAD_REQUEST,
353 + "dyncfg functions intercept: this job belongs to disabled template");
354 }
355 }
356
@@ -356,7 +371,7 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
371
372 dyncfg_apply_action_on_all_template_jobs(rfe, id, cmd);
373
359 - rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_OK, "applied");
374 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_OK, "applied to all template job");
375 make_the_call_to_plugin = false;
376 }
377 else if (cmd == DYNCFG_CMD_SCHEMA) {
src/daemon/config/dyncfg-tree.c
+33 -3
@@ -26,7 +26,7 @@ static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
26 buffer_json_member_add_string(wb, "template", string2str(df->template));
27
28 buffer_json_member_add_string(wb, "status", dyncfg_id2status(df->current.status));
29 - dyncfg_cmds2json_array(df->cmds, "cmds", wb);
29 + dyncfg_cmds2json_array(df->current.status == DYNCFG_STATUS_ORPHAN ? DYNCFG_CMD_REMOVE : df->cmds, "cmds", wb);
30 buffer_json_member_add_object(wb, "access");
31 {
32 http_access2buffer_json_array(wb, "view", df->view_access);
@@ -200,9 +200,39 @@ static int dyncfg_config_execute_cb(struct rrd_function_execute *rfe, void *data
200 dyncfg_tree_for_host(host, rfe->result.wb, path, id);
201 }
202 else {
203 + id = action;
204 + action = path;
205 + path = NULL;
206 +
207 + if(id && *id && dyncfg_cmds2id(action) == DYNCFG_CMD_REMOVE) {
208 + const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
209 + if(item) {
210 + DYNCFG *df = dictionary_acquired_item_value(item);
211 +
212 + if(!rrd_function_available(host, string2str(df->function)))
213 + df->current.status = DYNCFG_STATUS_ORPHAN;
214 +
215 + bool delete = (df->current.status == DYNCFG_STATUS_ORPHAN);
216 + dictionary_acquired_item_release(dyncfg_globals.nodes, item);
217 +
218 + if(delete) {
219 + dictionary_del(dyncfg_globals.nodes, id);
220 + dyncfg_file_delete(id);
221 + code = dyncfg_default_response(rfe->result.wb, 200, "");
222 + goto cleanup;
223 + }
224 + }
225 + }
226 +
227 code = HTTP_RESP_NOT_FOUND;
204 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: unknown config id '%s' in call: '%s'. This can happen if the plugin that registered the dynamic configuration is not running now.", action, rfe->function);
205 - rrd_call_function_error(rfe->result.wb, "unknown config id given", code);
228 + nd_log(NDLS_DAEMON, NDLP_ERR,
229 + "DYNCFG: unknown config id '%s' in call: '%s'. "
230 + "This can happen if the plugin that registered the dynamic configuration is not running now.",
231 + action, rfe->function);
232 +
233 + rrd_call_function_error(
234 + rfe->result.wb,
235 + "unknown config id given", code);
236 }
237
238 cleanup: