root/tools/cibadmin.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. print_xml_output
  2. report_schema_unchanged
  3. cib_action_is_dangerous
  4. scope_is_valid
  5. command_cb
  6. show_access_cb
  7. section_cb
  8. build_arg_context
  9. main
  10. do_work
  11. do_init

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <stdio.h>
  12 #include <crm/crm.h>
  13 #include <crm/common/cmdline_internal.h>
  14 #include <crm/common/ipc.h>
  15 #include <crm/common/xml.h>
  16 #include <crm/cib/internal.h>
  17 
  18 #include <pacemaker-internal.h>
  19 
  20 #define SUMMARY "query and edit the Pacemaker configuration"
  21 
  22 #define INDENT "                                "
  23 
  24 enum cibadmin_section_type {
  25     cibadmin_section_all = 0,
  26     cibadmin_section_scope,
  27     cibadmin_section_xpath,
  28 };
  29 
  30 static cib_t *the_cib = NULL;
  31 static crm_exit_t exit_code = CRM_EX_OK;
  32 
  33 static struct {
  34     const char *cib_action;
  35     int cmd_options;
  36     enum cibadmin_section_type section_type;
  37     char *cib_section;
  38     char *validate_with;
  39     gint message_timeout_sec;
  40     enum pcmk__acl_render_how acl_render_mode;
  41     gchar *cib_user;
  42     gchar *dest_node;
  43     gchar *input_file;
  44     gchar *input_xml;
  45     gboolean input_stdin;
  46     bool delete_all;
  47     gboolean allow_create;
  48     gboolean force;
  49     gboolean get_node_path;
  50     gboolean no_children;
  51     gboolean score_update;
  52 
  53     /* @COMPAT: For "-!" version option. Not advertised nor marked as
  54      * deprecated, but accepted.
  55      */
  56     gboolean extended_version;
  57 
  58     // @COMPAT Deprecated since 3.0.0
  59     gboolean local;
  60 
  61     // @COMPAT Deprecated since 3.0.1
  62     gboolean sync_call;
  63 } options = {
  64     .cmd_options = cib_sync_call,
  65 };
  66 
  67 int do_init(void);
  68 static int do_work(xmlNode *input, xmlNode **output);
  69 
  70 static void
  71 print_xml_output(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  72 {
  73     if (!xml) {
  74         return;
  75     } else if (xml->type != XML_ELEMENT_NODE) {
  76         return;
  77     }
  78 
  79     if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
  80         const char *id = crm_element_value(xml, PCMK_XA_ID);
  81 
  82         if (pcmk__xe_is(xml, PCMK__XE_XPATH_QUERY)) {
  83             xmlNode *child = NULL;
  84 
  85             for (child = xml->children; child; child = child->next) {
  86                 print_xml_output(child);
  87             }
  88 
  89         } else if (id) {
  90             printf("%s\n", id);
  91         }
  92 
  93     } else {
  94         GString *buf = g_string_sized_new(1024);
  95 
  96         pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buf, 0);
  97 
  98         fprintf(stdout, "%s", buf->str);
  99         g_string_free(buf, TRUE);
 100     }
 101 }
 102 
 103 // Upgrade requested but already at latest schema
 104 static void
 105 report_schema_unchanged(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 106 {
 107     const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
 108 
 109     crm_info("Upgrade unnecessary: %s\n", err);
 110     printf("Upgrade unnecessary: %s\n", err);
 111     exit_code = CRM_EX_OK;
 112 }
 113 
 114 /*!
 115  * \internal
 116  * \brief Check whether the current CIB action is dangerous
 117  * \return true if \p options.cib_action is dangerous, or false otherwise
 118  */
 119 static inline bool
 120 cib_action_is_dangerous(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 121 {
 122     /* @TODO Ideally, --upgrade wouldn't be considered dangerous if the CIB
 123      * already uses the latest schema.
 124      */
 125     return options.delete_all
 126            || pcmk__str_any_of(options.cib_action,
 127                                PCMK__CIB_REQUEST_UPGRADE,
 128                                PCMK__CIB_REQUEST_ERASE,
 129                                NULL);
 130 }
 131 
 132 /*!
 133  * \internal
 134  * \brief Determine whether the given CIB scope is valid for \p cibadmin
 135  *
 136  * \param[in] scope  Scope to validate
 137  *
 138  * \return true if \p scope is valid, or false otherwise
 139  * \note An invalid scope applies the operation to the entire CIB.
 140  */
 141 static inline bool
 142 scope_is_valid(const char *scope)
     /* [previous][next][first][last][top][bottom][index][help] */
 143 {
 144     return pcmk__str_any_of(scope,
 145                             PCMK_XE_CONFIGURATION,
 146                             PCMK_XE_NODES,
 147                             PCMK_XE_RESOURCES,
 148                             PCMK_XE_CONSTRAINTS,
 149                             PCMK_XE_CRM_CONFIG,
 150                             PCMK_XE_RSC_DEFAULTS,
 151                             PCMK_XE_OP_DEFAULTS,
 152                             PCMK_XE_ACLS,
 153                             PCMK_XE_FENCING_TOPOLOGY,
 154                             PCMK_XE_TAGS,
 155                             PCMK_XE_ALERTS,
 156                             PCMK_XE_STATUS,
 157                             NULL);
 158 }
 159 
 160 static gboolean
 161 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 162            GError **error)
 163 {
 164     options.delete_all = false;
 165 
 166     if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) {
 167         options.cib_action = PCMK__CIB_REQUEST_UPGRADE;
 168 
 169     } else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) {
 170         options.cib_action = PCMK__CIB_REQUEST_QUERY;
 171 
 172     } else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) {
 173         options.cib_action = PCMK__CIB_REQUEST_ERASE;
 174 
 175     } else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) {
 176         options.cib_action = PCMK__CIB_REQUEST_BUMP;
 177 
 178     } else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) {
 179         options.cib_action = PCMK__CIB_REQUEST_CREATE;
 180 
 181     } else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) {
 182         options.cib_action = PCMK__CIB_REQUEST_MODIFY;
 183 
 184     } else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) {
 185         options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH;
 186 
 187     } else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) {
 188         options.cib_action = PCMK__CIB_REQUEST_REPLACE;
 189 
 190     } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
 191         options.cib_action = PCMK__CIB_REQUEST_DELETE;
 192 
 193     } else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) {
 194         options.cib_action = PCMK__CIB_REQUEST_DELETE;
 195         options.delete_all = true;
 196 
 197     } else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) {
 198         options.cib_action = "empty";
 199         pcmk__str_update(&options.validate_with, optarg);
 200 
 201     } else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) {
 202         options.cib_action = "md5-sum";
 203 
 204     } else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned",
 205                                 NULL)) {
 206         options.cib_action = "md5-sum-versioned";
 207 
 208     } else {
 209         // Should be impossible
 210         return FALSE;
 211     }
 212 
 213     return TRUE;
 214 }
 215 
 216 static gboolean
 217 show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 218                GError **error)
 219 {
 220     if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) {
 221         options.acl_render_mode = pcmk__acl_render_default;
 222 
 223     } else if (g_strcmp0(optarg, "namespace") == 0) {
 224         options.acl_render_mode = pcmk__acl_render_namespace;
 225 
 226     } else if (g_strcmp0(optarg, "text") == 0) {
 227         options.acl_render_mode = pcmk__acl_render_text;
 228 
 229     } else if (g_strcmp0(optarg, "color") == 0) {
 230         options.acl_render_mode = pcmk__acl_render_color;
 231 
 232     } else {
 233         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 234                     "Invalid value '%s' for option '%s'",
 235                     optarg, option_name);
 236         return FALSE;
 237     }
 238     return TRUE;
 239 }
 240 
 241 static gboolean
 242 section_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 243            GError **error)
 244 {
 245     if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) {
 246         options.section_type = cibadmin_section_scope;
 247 
 248     } else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) {
 249         options.section_type = cibadmin_section_xpath;
 250 
 251     } else {
 252         // Should be impossible
 253         return FALSE;
 254     }
 255 
 256     pcmk__str_update(&options.cib_section, optarg);
 257     return TRUE;
 258 }
 259 
 260 static GOptionEntry command_entries[] = {
 261     { "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 262       "Upgrade the configuration to the latest syntax", NULL },
 263 
 264     { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 265       "Query the contents of the CIB", NULL },
 266 
 267     { "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 268       "Erase the contents of the whole CIB", NULL },
 269 
 270     { "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 271       "Increase the CIB's epoch value by 1", NULL },
 272 
 273     { "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 274       "Create an object in the CIB (will fail if object already exists)",
 275       NULL },
 276 
 277     { "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 278       "Find object somewhere in CIB's XML tree and update it (fails if object "
 279       "does not exist unless -c is also specified)",
 280       NULL },
 281 
 282     { "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 283       "Supply an update in the form of an XML diff (see crm_diff(8))", NULL },
 284 
 285     { "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 286       "Recursively replace an object in the CIB", NULL },
 287 
 288     { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 289       "Delete first object matching supplied criteria (for example, "
 290       "<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" "
 291           PCMK_XA_NAME "=\"monitor\"/>).\n"
 292       INDENT "The XML element name and all attributes must match in order for "
 293       "the element to be deleted.",
 294       NULL },
 295 
 296     { "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 297       command_cb,
 298       "When used with --xpath, remove all matching objects in the "
 299       "configuration instead of just the first one",
 300       NULL },
 301 
 302     { "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
 303       command_cb,
 304       "Output an empty CIB. Accepts an optional schema name argument to use as "
 305       "the " PCMK_XA_VALIDATE_WITH " value.\n"
 306       INDENT "If no schema is given, the latest will be used.",
 307       "[schema]" },
 308 
 309     { "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
 310       "Calculate the on-disk CIB digest", NULL },
 311 
 312     { "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 313       command_cb, "Calculate an on-the-wire versioned CIB digest", NULL },
 314 
 315     { NULL }
 316 };
 317 
 318 static GOptionEntry data_entries[] = {
 319     /* @COMPAT: These arguments should be last-wins. We can have an enum option
 320      * that stores the input type, along with a single string option that stores
 321      * the XML string for --xml-text, filename for --xml-file, or NULL for
 322      * --xml-pipe.
 323      */
 324     { "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
 325       &options.input_xml, "Retrieve XML from the supplied string", "value" },
 326 
 327     { "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME,
 328       &options.input_file, "Retrieve XML from the named file", "value" },
 329 
 330     { "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 331       &options.input_stdin, "Retrieve XML from stdin", NULL },
 332 
 333     { NULL }
 334 };
 335 
 336 static GOptionEntry addl_entries[] = {
 337     { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
 338       "Force the action to be performed", NULL },
 339 
 340     { "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT,
 341       &options.message_timeout_sec,
 342       "Time (in seconds) to wait before declaring the operation failed",
 343       "value" },
 344 
 345     { "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user,
 346       "Run the command with permissions of the named user (valid only for the "
 347       "root and " CRM_DAEMON_USER " accounts)", "value" },
 348 
 349     { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 350       "Limit scope of operation to specific section of CIB\n"
 351       INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " PCMK_XE_NODES
 352       ", " PCMK_XE_RESOURCES ", " PCMK_XE_CONSTRAINTS
 353       ", " PCMK_XE_CRM_CONFIG ", " PCMK_XE_RSC_DEFAULTS ",\n"
 354       INDENT "              " PCMK_XE_OP_DEFAULTS ", " PCMK_XE_ACLS
 355       ", " PCMK_XE_FENCING_TOPOLOGY ", " PCMK_XE_TAGS ", " PCMK_XE_ALERTS
 356       ", " PCMK_XE_STATUS "\n"
 357       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 358       "appear takes effect",
 359       "value" },
 360 
 361     { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
 362       "A valid XPath to use instead of --scope/-o\n"
 363       INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
 364       "appear takes effect",
 365       "value" },
 366 
 367     { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 368       &options.get_node_path,
 369       "When performing XPath queries, return paths of any matches found\n"
 370       INDENT "(for example, "
 371       "\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
 372       "/" PCMK_XE_RESOURCES "/" PCMK_XE_CLONE
 373       "[@" PCMK_XA_ID "='dummy-clone']"
 374       "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='dummy']\")",
 375       NULL },
 376 
 377     { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
 378       show_access_cb,
 379       "Whether to use syntax highlighting for ACLs (with -Q/--query and "
 380       "-U/--user)\n"
 381       INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
 382       "default for non-terminal),\n"
 383       INDENT "                'namespace', or 'auto' (use default value)\n"
 384       INDENT "Default value: 'auto'",
 385       "[value]" },
 386 
 387     { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update,
 388       "Treat new attribute values as atomic score updates where possible "
 389       "(with --modify/-M).\n"
 390 
 391       INDENT "This currently happens by default and cannot be disabled, but\n"
 392       INDENT "this default behavior is deprecated and will be removed in a\n"
 393       INDENT "future release. Set this flag if this behavior is desired.\n"
 394 
 395       INDENT "This option takes effect when updating XML attributes. For an\n"
 396       INDENT "attribute named \"name\", if the new value is \"name++\" or\n"
 397       INDENT "\"name+=X\" for some score X, the new value is set as follows:\n"
 398       INDENT "If attribute \"name\" is not already set to some value in\n"
 399       INDENT "the element being updated, the new value is set as a literal\n"
 400       INDENT "string.\n"
 401       INDENT "If the new value is \"name++\", then the attribute is set to \n"
 402       INDENT "its existing value (parsed as a score) plus 1.\n"
 403       INDENT "If the new value is \"name+=X\" for some score X, then the\n"
 404       INDENT "attribute is set to its existing value plus X, where the\n"
 405       INDENT "existing value and X are parsed and added as scores.\n"
 406 
 407       INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n"
 408       INDENT "Refer to Pacemaker Explained for more details on scores,\n"
 409       INDENT "including how they are parsed and added.",
 410       NULL },
 411 
 412     { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 413       &options.allow_create,
 414       "(Advanced) Allow target of --modify/-M to be created if it does not "
 415       "exist",
 416       NULL },
 417 
 418     { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
 419       &options.no_children,
 420       "(Advanced) When querying an object, do not include its children in the "
 421       "result",
 422       NULL },
 423 
 424     { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
 425       "(Advanced) Send command to the specified host", "value" },
 426 
 427     // @COMPAT Deprecated since 3.0.0
 428     { "local", 'l', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.local,
 429       "(deprecated)", NULL },
 430 
 431     // @COMPAT Deprecated since 3.0.1
 432     { "sync-call", 's', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
 433       &options.sync_call, "(deprecated)", NULL },
 434 
 435     { NULL }
 436 };
 437 
 438 static GOptionContext *
 439 build_arg_context(pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
 440 {
 441     const char *desc = NULL;
 442     GOptionContext *context = NULL;
 443 
 444     GOptionEntry extra_prog_entries[] = {
 445         // @COMPAT: Deprecated
 446         { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
 447           &options.extended_version, "deprecated", NULL },
 448 
 449         { NULL }
 450     };
 451 
 452     desc = "Examples:\n\n"
 453            "Query the configuration:\n\n"
 454            "\t# cibadmin --query\n\n"
 455            "Query just the cluster options configuration:\n\n"
 456            "\t# cibadmin --query --scope " PCMK_XE_CRM_CONFIG "\n\n"
 457            "Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n"
 458            "\t# cibadmin --query --xpath "
 459                "\"//" PCMK_XE_NVPAIR
 460                "[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\"\n\n"
 461            "Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n"
 462            "\t# cibadmin --delete-all --xpath "
 463                "\"//" PCMK_XE_NVPAIR
 464                "[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n"
 465            "Remove the resource named 'old':\n\n"
 466            "\t# cibadmin --delete --xml-text "
 467                "'<" PCMK_XE_PRIMITIVE " " PCMK_XA_ID "=\"old\"/>'\n\n"
 468            "Remove all resources from the configuration:\n\n"
 469            "\t# cibadmin --replace --scope " PCMK_XE_RESOURCES
 470                " --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n"
 471            "Replace complete configuration with contents of "
 472                "$HOME/pacemaker.xml:\n\n"
 473            "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
 474            "Replace " PCMK_XE_CONSTRAINTS " section of configuration with "
 475                "contents of $HOME/constraints.xml:\n\n"
 476            "\t# cibadmin --replace --scope " PCMK_XE_CONSTRAINTS
 477                " --xml-file $HOME/constraints.xml\n\n"
 478            "Increase configuration version to prevent old configurations from "
 479                "being loaded accidentally:\n\n"
 480            "\t# cibadmin --modify --score --xml-text "
 481                "'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH
 482                    "=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n"
 483            "Edit the configuration with your favorite $EDITOR:\n\n"
 484            "\t# cibadmin --query > $HOME/local.xml\n\n"
 485            "\t# $EDITOR $HOME/local.xml\n\n"
 486            "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
 487            "Assuming terminal, render configuration in color (green for "
 488                "writable, blue for readable, red for\n"
 489                "denied) to visualize permissions for user tony:\n\n"
 490            "\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
 491            "SEE ALSO:\n"
 492            " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
 493 
 494     context = pcmk__build_arg_context(args, NULL, NULL, "<command>");
 495     g_option_context_set_description(context, desc);
 496 
 497     pcmk__add_main_args(context, extra_prog_entries);
 498 
 499     pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
 500                         command_entries);
 501     pcmk__add_arg_group(context, "data", "Data:", "Show data help",
 502                         data_entries);
 503     pcmk__add_arg_group(context, "additional", "Additional Options:",
 504                         "Show additional options", addl_entries);
 505     return context;
 506 }
 507 
 508 int
 509 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 510 {
 511     int rc = pcmk_rc_ok;
 512     const char *source = NULL;
 513     xmlNode *output = NULL;
 514     xmlNode *input = NULL;
 515     gchar *acl_cred = NULL;
 516 
 517     GError *error = NULL;
 518 
 519     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 520     gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
 521     GOptionContext *context = build_arg_context(args);
 522 
 523     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 524         exit_code = CRM_EX_USAGE;
 525         goto done;
 526     }
 527 
 528     if (g_strv_length(processed_args) > 1) {
 529         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 530         GString *extra = g_string_sized_new(128);
 531 
 532         for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
 533             if (extra->len > 0) {
 534                 g_string_append_c(extra, ' ');
 535             }
 536             g_string_append(extra, processed_args[lpc]);
 537         }
 538 
 539         exit_code = CRM_EX_USAGE;
 540         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 541                     "non-option ARGV-elements: %s\n\n%s", extra->str, help);
 542         g_free(help);
 543         g_string_free(extra, TRUE);
 544         goto done;
 545     }
 546 
 547     if (args->version || options.extended_version) {
 548         g_strfreev(processed_args);
 549         pcmk__free_arg_context(context);
 550 
 551         /* FIXME: When cibadmin is converted to use formatted output, this can
 552          * be replaced by out->version with the appropriate boolean flag.
 553          *
 554          * options.extended_version is deprecated and will be removed in a
 555          * future release.
 556          */
 557         pcmk__cli_help(options.extended_version? '!' : 'v');
 558     }
 559 
 560     /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
 561      *
 562      * (func@file:line)      error: CIB <op> failures   <XML>
 563      *
 564      * In cibadmin we explicitly output the XML portion without the prefixes. So
 565      * we default to LOG_CRIT.
 566      */
 567     pcmk__cli_init_logging("cibadmin", 0);
 568     set_crm_log_level(LOG_CRIT);
 569 
 570     if (args->verbosity > 0) {
 571         cib__set_call_options(options.cmd_options, crm_system_name,
 572                               cib_verbose);
 573 
 574         for (int i = 0; i < args->verbosity; i++) {
 575             crm_bump_log_level(argc, argv);
 576         }
 577     }
 578 
 579     if (options.cib_action == NULL) {
 580         // @COMPAT: Create a default command if other tools have one
 581         gchar *help = g_option_context_get_help(context, TRUE, NULL);
 582 
 583         exit_code = CRM_EX_USAGE;
 584         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 585                     "Must specify a command option\n\n%s", help);
 586         g_free(help);
 587         goto done;
 588     }
 589 
 590     if (strcmp(options.cib_action, "empty") == 0) {
 591         // Output an empty CIB
 592         GString *buf = g_string_sized_new(1024);
 593 
 594         output = createEmptyCib(1);
 595         crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
 596 
 597         pcmk__xml_string(output, pcmk__xml_fmt_pretty, buf, 0);
 598         fprintf(stdout, "%s", buf->str);
 599         g_string_free(buf, TRUE);
 600         goto done;
 601     }
 602 
 603     if (cib_action_is_dangerous() && !options.force) {
 604         exit_code = CRM_EX_UNSAFE;
 605         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 606                     "The supplied command is considered dangerous. To prevent "
 607                     "accidental destruction of the cluster, the --force flag "
 608                     "is required in order to proceed.");
 609         goto done;
 610     }
 611 
 612     if (options.message_timeout_sec < 1) {
 613         // Set default timeout
 614         options.message_timeout_sec = 30;
 615     }
 616 
 617     if (options.section_type == cibadmin_section_xpath) {
 618         // Enable getting section by XPath
 619         cib__set_call_options(options.cmd_options, crm_system_name,
 620                               cib_xpath);
 621 
 622     } else if (options.section_type == cibadmin_section_scope) {
 623         if (!scope_is_valid(options.cib_section)) {
 624             // @COMPAT: Consider requiring --force to proceed
 625             fprintf(stderr,
 626                     "Invalid value '%s' for '--scope'. Operation will apply "
 627                     "to the entire CIB.\n", options.cib_section);
 628         }
 629     }
 630 
 631     if (options.allow_create) {
 632         // Allow target of --modify/-M to be created if it does not exist
 633         cib__set_call_options(options.cmd_options, crm_system_name,
 634                               cib_can_create);
 635     }
 636 
 637     if (options.delete_all) {
 638         // With cibadmin_section_xpath, remove all matching objects
 639         cib__set_call_options(options.cmd_options, crm_system_name,
 640                               cib_multiple);
 641     }
 642 
 643     if (options.get_node_path) {
 644         /* Enable getting node path of XPath query matches.
 645          * Meaningful only if options.section_type == cibadmin_section_xpath.
 646          */
 647         cib__set_call_options(options.cmd_options, crm_system_name,
 648                               cib_xpath_address);
 649     }
 650 
 651     if (options.no_children) {
 652         // When querying an object, don't include its children in the result
 653         cib__set_call_options(options.cmd_options, crm_system_name,
 654                               cib_no_children);
 655     }
 656 
 657     if (options.input_file != NULL) {
 658         input = pcmk__xml_read(options.input_file);
 659         source = options.input_file;
 660 
 661     } else if (options.input_xml != NULL) {
 662         input = pcmk__xml_parse(options.input_xml);
 663         source = "input string";
 664 
 665     } else if (options.input_stdin) {
 666         input = pcmk__xml_read(NULL);
 667         source = "STDIN";
 668 
 669     } else if (options.acl_render_mode != pcmk__acl_render_none) {
 670         char *username = pcmk__uid2username(geteuid());
 671         bool required = pcmk_acl_required(username);
 672 
 673         free(username);
 674 
 675         if (required) {
 676             if (options.force) {
 677                 fprintf(stderr, "The supplied command can provide skewed"
 678                                  " result since it is run under user that also"
 679                                  " gets guarded per ACLs on their own right."
 680                                  " Continuing since --force flag was"
 681                                  " provided.\n");
 682 
 683             } else {
 684                 exit_code = CRM_EX_UNSAFE;
 685                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 686                             "The supplied command can provide skewed result "
 687                             "since it is run under user that also gets guarded "
 688                             "per ACLs in their own right. To accept the risk "
 689                             "of such a possible distortion (without even "
 690                             "knowing it at this time), use the --force flag.");
 691                 goto done;
 692             }
 693         }
 694 
 695         if (options.cib_user == NULL) {
 696             exit_code = CRM_EX_USAGE;
 697             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 698                         "The supplied command requires -U user specified.");
 699             goto done;
 700         }
 701 
 702         /* We already stopped/warned ACL-controlled users about consequences.
 703          *
 704          * Note: acl_cred takes ownership of options.cib_user here.
 705          * options.cib_user is set to NULL so that the CIB is obtained as the
 706          * user running the cibadmin command. The CIB must be obtained as a user
 707          * with full permissions in order to show the CIB correctly annotated
 708          * for the options.cib_user's permissions.
 709          */
 710         acl_cred = options.cib_user;
 711         options.cib_user = NULL;
 712     }
 713 
 714     if (input != NULL) {
 715         crm_log_xml_debug(input, "[admin input]");
 716 
 717     } else if (source != NULL) {
 718         exit_code = CRM_EX_CONFIG;
 719         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 720                     "Couldn't parse input from %s.", source);
 721         goto done;
 722     }
 723 
 724     if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) {
 725         char *digest = NULL;
 726 
 727         if (input == NULL) {
 728             exit_code = CRM_EX_USAGE;
 729             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 730                         "Please supply XML to process with -X, -x, or -p");
 731             goto done;
 732         }
 733 
 734         digest = pcmk__digest_on_disk_cib(input);
 735         fprintf(stderr, "Digest: ");
 736         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 737         free(digest);
 738         goto done;
 739 
 740     } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
 741         char *digest = NULL;
 742 
 743         if (input == NULL) {
 744             exit_code = CRM_EX_USAGE;
 745             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 746                         "Please supply XML to process with -X, -x, or -p");
 747             goto done;
 748         }
 749 
 750         digest = pcmk__digest_xml(input, true);
 751         fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
 752         free(digest);
 753         goto done;
 754 
 755     } else if (pcmk__str_eq(options.cib_action, PCMK__CIB_REQUEST_MODIFY,
 756                             pcmk__str_none)) {
 757         /* @COMPAT When we drop default support for expansion in cibadmin, guard
 758          * with `if (options.score_update)`
 759          */
 760         cib__set_call_options(options.cmd_options, crm_system_name,
 761                               cib_score_update);
 762     }
 763 
 764     rc = do_init();
 765     if (rc != pcmk_ok) {
 766         rc = pcmk_legacy2rc(rc);
 767         exit_code = pcmk_rc2exitc(rc);
 768 
 769         crm_err("Init failed, could not perform requested operations: %s",
 770                 pcmk_rc_str(rc));
 771         g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 772                     "Init failed, could not perform requested operations: %s",
 773                     pcmk_rc_str(rc));
 774         goto done;
 775     }
 776 
 777     rc = do_work(input, &output);
 778     rc = pcmk_legacy2rc(rc);
 779 
 780     if ((rc == pcmk_rc_schema_unchanged)
 781         && (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0)) {
 782 
 783         report_schema_unchanged();
 784 
 785     } else if (rc != pcmk_rc_ok) {
 786         crm_err("Call failed: %s", pcmk_rc_str(rc));
 787         fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
 788         exit_code = pcmk_rc2exitc(rc);
 789 
 790         if (rc == pcmk_rc_schema_validation) {
 791             if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) {
 792                 xmlNode *obj = NULL;
 793 
 794                 if (the_cib->cmds->query(the_cib, NULL, &obj,
 795                                          options.cmd_options) == pcmk_ok) {
 796                     pcmk__update_schema(&obj, NULL, true, false);
 797                 }
 798                 pcmk__xml_free(obj);
 799 
 800             } else if (output != NULL) {
 801                 // Show validation errors to stderr
 802                 pcmk__validate_xml(output, NULL, NULL, NULL);
 803             }
 804         }
 805     }
 806 
 807     if ((output != NULL)
 808         && (options.acl_render_mode != pcmk__acl_render_none)) {
 809 
 810         xmlDoc *acl_evaled_doc;
 811         rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
 812         if (rc == pcmk_rc_ok) {
 813             xmlChar *rendered = NULL;
 814 
 815             rc = pcmk__acl_evaled_render(acl_evaled_doc,
 816                                          options.acl_render_mode, &rendered);
 817             if (rc != pcmk_rc_ok) {
 818                 exit_code = CRM_EX_CONFIG;
 819                 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 820                             "Could not render evaluated access: %s",
 821                             pcmk_rc_str(rc));
 822                 goto done;
 823             }
 824             printf("%s\n", (char *) rendered);
 825             free(rendered);
 826 
 827         } else {
 828             exit_code = CRM_EX_CONFIG;
 829             g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
 830                         "Could not evaluate access per request (%s, error: %s)",
 831                         acl_cred, pcmk_rc_str(rc));
 832             goto done;
 833         }
 834 
 835     } else if (output != NULL) {
 836         print_xml_output(output);
 837     }
 838 
 839     crm_trace("%s exiting normally", crm_system_name);
 840 
 841 done:
 842     g_strfreev(processed_args);
 843     pcmk__free_arg_context(context);
 844 
 845     g_free(options.cib_user);
 846     g_free(options.dest_node);
 847     g_free(options.input_file);
 848     g_free(options.input_xml);
 849     free(options.cib_section);
 850     free(options.validate_with);
 851 
 852     g_free(acl_cred);
 853     pcmk__xml_free(input);
 854     pcmk__xml_free(output);
 855 
 856     rc = cib__clean_up_connection(&the_cib);
 857     if (exit_code == CRM_EX_OK) {
 858         exit_code = pcmk_rc2exitc(rc);
 859     }
 860 
 861     pcmk__output_and_clear_error(&error, NULL);
 862     crm_exit(exit_code);
 863 }
 864 
 865 static int
 866 do_work(xmlNode *input, xmlNode **output)
     /* [previous][next][first][last][top][bottom][index][help] */
 867 {
 868     /* construct the request */
 869     the_cib->call_timeout = options.message_timeout_sec;
 870     if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
 871         && pcmk__xe_is(input, PCMK_XE_CIB)) {
 872         xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS);
 873 
 874         if (status == NULL) {
 875             pcmk__xe_create(input, PCMK_XE_STATUS);
 876         }
 877     }
 878 
 879     crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
 880     return cib_internal_op(the_cib, options.cib_action, options.dest_node,
 881                            options.cib_section, input, output,
 882                            options.cmd_options, options.cib_user);
 883 }
 884 
 885 int
 886 do_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 887 {
 888     int rc = pcmk_ok;
 889 
 890     the_cib = cib_new();
 891     rc = cib__signon_attempts(the_cib, cib_command, 5);
 892     if (rc != pcmk_ok) {
 893         crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
 894         fprintf(stderr, "Could not connect to the CIB: %s\n",
 895                 pcmk_strerror(rc));
 896     }
 897 
 898     return rc;
 899 }

/* [previous][next][first][last][top][bottom][index][help] */