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

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