root/tools/crm_diff.c

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

DEFINITIONS

This source file includes following definitions.
  1. new_string_cb
  2. original_string_cb
  3. patch_cb
  4. print_patch
  5. apply_patch
  6. log_patch_cib_versions
  7. strip_patch_cib_version
  8. generate_patch
  9. build_arg_context
  10. main

   1 /*
   2  * Copyright 2005-2023 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 
  12 #include <stdio.h>
  13 #include <unistd.h>
  14 #include <stdlib.h>
  15 #include <errno.h>
  16 #include <fcntl.h>
  17 #include <sys/param.h>
  18 #include <sys/types.h>
  19 
  20 #include <crm/crm.h>
  21 #include <crm/msg_xml.h>
  22 #include <crm/common/cmdline_internal.h>
  23 #include <crm/common/output_internal.h>
  24 #include <crm/common/xml.h>
  25 #include <crm/common/ipc.h>
  26 #include <crm/cib.h>
  27 
  28 #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
  29                 "or apply such an output as a patch"
  30 
  31 struct {
  32     gboolean apply;
  33     gboolean as_cib;
  34     gboolean no_version;
  35     gboolean raw_1;
  36     gboolean raw_2;
  37     gboolean use_stdin;
  38     char *xml_file_1;
  39     char *xml_file_2;
  40 } options;
  41 
  42 gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  43 gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  44 gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
  45 
  46 static GOptionEntry original_xml_entries[] = {
  47     { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
  48       "XML is contained in the named file",
  49       "FILE" },
  50     { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
  51       "XML is contained in the supplied string",
  52       "STRING" },
  53 
  54     { NULL }
  55 };
  56 
  57 static GOptionEntry operation_entries[] = {
  58     { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
  59       "Compare the original XML to the contents of the named file",
  60       "FILE" },
  61     { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
  62       "Compare the original XML with the contents of the supplied string",
  63       "STRING" },
  64     { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
  65       "Patch the original XML with the contents of the named file",
  66       "FILE" },
  67 
  68     { NULL }
  69 };
  70 
  71 static GOptionEntry addl_entries[] = {
  72     { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
  73       "Compare/patch the inputs as a CIB (includes versions details)",
  74       NULL },
  75     { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
  76       "",
  77       NULL },
  78     { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
  79       "Generate the difference without versions details",
  80       NULL },
  81 
  82     { NULL }
  83 };
  84 
  85 gboolean
  86 new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  87     options.raw_2 = TRUE;
  88     pcmk__str_update(&options.xml_file_2, optarg);
  89     return TRUE;
  90 }
  91 
  92 gboolean
  93 original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  94     options.raw_1 = TRUE;
  95     pcmk__str_update(&options.xml_file_1, optarg);
  96     return TRUE;
  97 }
  98 
  99 gboolean
 100 patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 101     options.apply = TRUE;
 102     pcmk__str_update(&options.xml_file_2, optarg);
 103     return TRUE;
 104 }
 105 
 106 static void
 107 print_patch(xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 108 {
 109     char *buffer = dump_xml_formatted(patch);
 110 
 111     printf("%s", pcmk__s(buffer, "<null>\n"));
 112     free(buffer);
 113     fflush(stdout);
 114 }
 115 
 116 // \return Standard Pacemaker return code
 117 static int
 118 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 119 {
 120     xmlNode *output = copy_xml(input);
 121     int rc = xml_apply_patchset(output, patch, as_cib);
 122 
 123     rc = pcmk_legacy2rc(rc);
 124     if (rc != pcmk_rc_ok) {
 125         fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc));
 126         free_xml(output);
 127         return rc;
 128     }
 129 
 130     if (output != NULL) {
 131         const char *version;
 132         char *buffer;
 133 
 134         print_patch(output);
 135 
 136         version = crm_element_value(output, XML_ATTR_CRM_VERSION);
 137         buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
 138         crm_trace("Digest: %s", pcmk__s(buffer, "<null>\n"));
 139         free(buffer);
 140         free_xml(output);
 141     }
 142     return pcmk_rc_ok;
 143 }
 144 
 145 static void
 146 log_patch_cib_versions(xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 147 {
 148     int add[] = { 0, 0, 0 };
 149     int del[] = { 0, 0, 0 };
 150 
 151     const char *fmt = NULL;
 152     const char *digest = NULL;
 153 
 154     xml_patch_versions(patch, add, del);
 155     fmt = crm_element_value(patch, "format");
 156     digest = crm_element_value(patch, XML_ATTR_DIGEST);
 157 
 158     if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
 159         crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
 160         crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
 161     }
 162 }
 163 
 164 static void
 165 strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields)
     /* [previous][next][first][last][top][bottom][index][help] */
 166 {
 167     int format = 1;
 168 
 169     crm_element_value_int(patch, "format", &format);
 170     if (format == 2) {
 171         xmlNode *version_xml = find_xml_node(patch, "version", FALSE);
 172 
 173         if (version_xml) {
 174             free_xml(version_xml);
 175         }
 176 
 177     } else {
 178         int i = 0;
 179 
 180         const char *tags[] = {
 181             XML_TAG_DIFF_REMOVED,
 182             XML_TAG_DIFF_ADDED,
 183         };
 184 
 185         for (i = 0; i < PCMK__NELEM(tags); i++) {
 186             xmlNode *tmp = NULL;
 187             int lpc;
 188 
 189             tmp = find_xml_node(patch, tags[i], FALSE);
 190             if (tmp) {
 191                 for (lpc = 0; lpc < nvfields; lpc++) {
 192                     xml_remove_prop(tmp, vfields[lpc]);
 193                 }
 194 
 195                 tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
 196                 if (tmp) {
 197                     for (lpc = 0; lpc < nvfields; lpc++) {
 198                         xml_remove_prop(tmp, vfields[lpc]);
 199                     }
 200                 }
 201             }
 202         }
 203     }
 204 }
 205 
 206 // \return Standard Pacemaker return code
 207 static int
 208 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
     /* [previous][next][first][last][top][bottom][index][help] */
 209                gboolean as_cib, gboolean no_version)
 210 {
 211     xmlNode *output = NULL;
 212     int rc = pcmk_rc_ok;
 213 
 214     pcmk__output_t *logger_out = NULL;
 215     int out_rc = pcmk_rc_no_output;
 216     int temp_rc = pcmk_rc_no_output;
 217 
 218     const char *vfields[] = {
 219         XML_ATTR_GENERATION_ADMIN,
 220         XML_ATTR_GENERATION,
 221         XML_ATTR_NUMUPDATES,
 222     };
 223 
 224     rc = pcmk__log_output_new(&logger_out);
 225     CRM_CHECK(rc == pcmk_rc_ok, return rc);
 226 
 227     /* If we're ignoring the version, make the version information
 228      * identical, so it isn't detected as a change. */
 229     if (no_version) {
 230         int lpc;
 231 
 232         for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 233             crm_copy_xml_element(object_1, object_2, vfields[lpc]);
 234         }
 235     }
 236 
 237     xml_track_changes(object_2, NULL, object_2, FALSE);
 238     if(as_cib) {
 239         xml_calculate_significant_changes(object_1, object_2);
 240     } else {
 241         xml_calculate_changes(object_1, object_2);
 242     }
 243     crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
 244 
 245     output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
 246 
 247     pcmk__output_set_log_level(logger_out, LOG_INFO);
 248     out_rc = pcmk__xml_show_changes(logger_out, object_2);
 249 
 250     xml_accept_changes(object_2);
 251 
 252     if (output == NULL) {
 253         goto done;  // rc == pcmk_rc_ok
 254     }
 255 
 256     /* pcmk_rc_error means there's non-empty diff.
 257      * @COMPAT: Choose a more descriptive return code, like one that maps to
 258      * CRM_EX_DIGEST?
 259      */
 260     rc = pcmk_rc_error;
 261 
 262     patchset_process_digest(output, object_1, object_2, as_cib);
 263 
 264     if (as_cib) {
 265         log_patch_cib_versions(output);
 266 
 267     } else if (no_version) {
 268         strip_patch_cib_version(output, vfields, PCMK__NELEM(vfields));
 269     }
 270 
 271     pcmk__output_set_log_level(logger_out, LOG_NOTICE);
 272     temp_rc = logger_out->message(logger_out, "xml-patchset", output);
 273     out_rc = pcmk__output_select_rc(out_rc, temp_rc);
 274 
 275     print_patch(output);
 276     free_xml(output);
 277 
 278 done:
 279     logger_out->finish(logger_out, pcmk_rc2exitc(out_rc), true, NULL);
 280     pcmk__output_free(logger_out);
 281 
 282     return rc;
 283 }
 284 
 285 static GOptionContext *
 286 build_arg_context(pcmk__common_args_t *args) {
     /* [previous][next][first][last][top][bottom][index][help] */
 287     GOptionContext *context = NULL;
 288 
 289     const char *description = "Examples:\n\n"
 290                               "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
 291                               "\t# cibadmin --query > cib-old.xml\n\n"
 292                               "\t# cibadmin --query > cib-new.xml\n\n"
 293                               "Calculate and save the difference between the two files:\n\n"
 294                               "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
 295                               "Apply the patch to the original file:\n\n"
 296                               "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
 297                               "Apply the patch to the running cluster:\n\n"
 298                               "\t# cibadmin --patch -x patch.xml\n";
 299 
 300     context = pcmk__build_arg_context(args, NULL, NULL, NULL);
 301     g_option_context_set_description(context, description);
 302 
 303     pcmk__add_arg_group(context, "xml", "Original XML:",
 304                         "Show original XML options", original_xml_entries);
 305     pcmk__add_arg_group(context, "operation", "Operation:",
 306                         "Show operation options", operation_entries);
 307     pcmk__add_arg_group(context, "additional", "Additional Options:",
 308                         "Show additional options", addl_entries);
 309     return context;
 310 }
 311 
 312 int
 313 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 314 {
 315     xmlNode *object_1 = NULL;
 316     xmlNode *object_2 = NULL;
 317 
 318     crm_exit_t exit_code = CRM_EX_OK;
 319     GError *error = NULL;
 320 
 321     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 322     gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
 323     GOptionContext *context = build_arg_context(args);
 324 
 325     int rc = pcmk_rc_ok;
 326 
 327     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 328         exit_code = CRM_EX_USAGE;
 329         goto done;
 330     }
 331 
 332     pcmk__cli_init_logging("crm_diff", args->verbosity);
 333 
 334     if (args->version) {
 335         g_strfreev(processed_args);
 336         pcmk__free_arg_context(context);
 337         /* FIXME:  When crm_diff is converted to use formatted output, this can go. */
 338         pcmk__cli_help('v');
 339     }
 340 
 341     if (options.apply && options.no_version) {
 342         fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
 343     } else if (options.as_cib && options.no_version) {
 344         fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
 345         exit_code = CRM_EX_USAGE;
 346         goto done;
 347     }
 348 
 349     if (options.raw_1) {
 350         object_1 = string2xml(options.xml_file_1);
 351 
 352     } else if (options.use_stdin) {
 353         fprintf(stderr, "Input first XML fragment:");
 354         object_1 = stdin2xml();
 355 
 356     } else if (options.xml_file_1 != NULL) {
 357         object_1 = filename2xml(options.xml_file_1);
 358     }
 359 
 360     if (options.raw_2) {
 361         object_2 = string2xml(options.xml_file_2);
 362 
 363     } else if (options.use_stdin) {
 364         fprintf(stderr, "Input second XML fragment:");
 365         object_2 = stdin2xml();
 366 
 367     } else if (options.xml_file_2 != NULL) {
 368         object_2 = filename2xml(options.xml_file_2);
 369     }
 370 
 371     if (object_1 == NULL) {
 372         fprintf(stderr, "Could not parse the first XML fragment\n");
 373         exit_code = CRM_EX_DATAERR;
 374         goto done;
 375     }
 376     if (object_2 == NULL) {
 377         fprintf(stderr, "Could not parse the second XML fragment\n");
 378         exit_code = CRM_EX_DATAERR;
 379         goto done;
 380     }
 381 
 382     if (options.apply) {
 383         rc = apply_patch(object_1, object_2, options.as_cib);
 384     } else {
 385         rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
 386     }
 387     exit_code = pcmk_rc2exitc(rc);
 388 
 389 done:
 390     g_strfreev(processed_args);
 391     pcmk__free_arg_context(context);
 392     free(options.xml_file_1);
 393     free(options.xml_file_2);
 394     free_xml(object_1);
 395     free_xml(object_2);
 396 
 397     pcmk__output_and_clear_error(&error, NULL);
 398     crm_exit(exit_code);
 399 }

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