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-2018 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/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 
  88     if (options.xml_file_2 != NULL) {
  89         free(options.xml_file_2);
  90     }
  91 
  92     options.xml_file_2 = strdup(optarg);
  93     return TRUE;
  94 }
  95 
  96 gboolean
  97 original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  98     options.raw_1 = TRUE;
  99 
 100     if (options.xml_file_1 != NULL) {
 101         free(options.xml_file_1);
 102     }
 103 
 104     options.xml_file_1 = strdup(optarg);
 105     return TRUE;
 106 }
 107 
 108 gboolean
 109 patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 110     options.apply = TRUE;
 111 
 112     if (options.xml_file_2 != NULL) {
 113         free(options.xml_file_2);
 114     }
 115 
 116     options.xml_file_2 = strdup(optarg);
 117     return TRUE;
 118 }
 119 
 120 static void
 121 print_patch(xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 122 {
 123     char *buffer = dump_xml_formatted(patch);
 124 
 125     printf("%s\n", crm_str(buffer));
 126     free(buffer);
 127     fflush(stdout);
 128 }
 129 
 130 static int
 131 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 132 {
 133     int rc;
 134     xmlNode *output = copy_xml(input);
 135 
 136     rc = xml_apply_patchset(output, patch, as_cib);
 137     if (rc != pcmk_ok) {
 138         fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc));
 139         free_xml(output);
 140         return rc;
 141     }
 142 
 143     if (output != NULL) {
 144         const char *version;
 145         char *buffer;
 146 
 147         print_patch(output);
 148 
 149         version = crm_element_value(output, XML_ATTR_CRM_VERSION);
 150         buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
 151         crm_trace("Digest: %s\n", crm_str(buffer));
 152         free(buffer);
 153         free_xml(output);
 154     }
 155     return pcmk_ok;
 156 }
 157 
 158 static void
 159 log_patch_cib_versions(xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 160 {
 161     int add[] = { 0, 0, 0 };
 162     int del[] = { 0, 0, 0 };
 163 
 164     const char *fmt = NULL;
 165     const char *digest = NULL;
 166 
 167     xml_patch_versions(patch, add, del);
 168     fmt = crm_element_value(patch, "format");
 169     digest = crm_element_value(patch, XML_ATTR_DIGEST);
 170 
 171     if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
 172         crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
 173         crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
 174     }
 175 }
 176 
 177 static void
 178 strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields)
     /* [previous][next][first][last][top][bottom][index][help] */
 179 {
 180     int format = 1;
 181 
 182     crm_element_value_int(patch, "format", &format);
 183     if (format == 2) {
 184         xmlNode *version_xml = find_xml_node(patch, "version", FALSE);
 185 
 186         if (version_xml) {
 187             free_xml(version_xml);
 188         }
 189 
 190     } else {
 191         int i = 0;
 192 
 193         const char *tags[] = {
 194             XML_TAG_DIFF_REMOVED,
 195             XML_TAG_DIFF_ADDED,
 196         };
 197 
 198         for (i = 0; i < DIMOF(tags); i++) {
 199             xmlNode *tmp = NULL;
 200             int lpc;
 201 
 202             tmp = find_xml_node(patch, tags[i], FALSE);
 203             if (tmp) {
 204                 for (lpc = 0; lpc < nvfields; lpc++) {
 205                     xml_remove_prop(tmp, vfields[lpc]);
 206                 }
 207 
 208                 tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
 209                 if (tmp) {
 210                     for (lpc = 0; lpc < nvfields; lpc++) {
 211                         xml_remove_prop(tmp, vfields[lpc]);
 212                     }
 213                 }
 214             }
 215         }
 216     }
 217 }
 218 
 219 static int
 220 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
     /* [previous][next][first][last][top][bottom][index][help] */
 221                gboolean as_cib, gboolean no_version)
 222 {
 223     xmlNode *output = NULL;
 224 
 225     const char *vfields[] = {
 226         XML_ATTR_GENERATION_ADMIN,
 227         XML_ATTR_GENERATION,
 228         XML_ATTR_NUMUPDATES,
 229     };
 230 
 231     /* If we're ignoring the version, make the version information
 232      * identical, so it isn't detected as a change. */
 233     if (no_version) {
 234         int lpc;
 235 
 236         for (lpc = 0; lpc < DIMOF(vfields); lpc++) {
 237             crm_copy_xml_element(object_1, object_2, vfields[lpc]);
 238         }
 239     }
 240 
 241     xml_track_changes(object_2, NULL, object_2, FALSE);
 242     if(as_cib) {
 243         xml_calculate_significant_changes(object_1, object_2);
 244     } else {
 245         xml_calculate_changes(object_1, object_2);
 246     }
 247     crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
 248 
 249     output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
 250 
 251     xml_log_changes(LOG_INFO, __func__, object_2);
 252     xml_accept_changes(object_2);
 253 
 254     if (output == NULL) {
 255         return pcmk_ok;
 256     }
 257 
 258     patchset_process_digest(output, object_1, object_2, as_cib);
 259 
 260     if (as_cib) {
 261         log_patch_cib_versions(output);
 262 
 263     } else if (no_version) {
 264         strip_patch_cib_version(output, vfields, DIMOF(vfields));
 265     }
 266 
 267     xml_log_patchset(LOG_NOTICE, __func__, output);
 268     print_patch(output);
 269     free_xml(output);
 270     return -pcmk_err_generic;
 271 }
 272 
 273 static GOptionContext *
 274 build_arg_context(pcmk__common_args_t *args) {
     /* [previous][next][first][last][top][bottom][index][help] */
 275     GOptionContext *context = NULL;
 276 
 277     const char *description = "Examples:\n\n"
 278                               "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
 279                               "\t# cibadmin --query > cib-old.xml\n\n"
 280                               "\t# cibadmin --query > cib-new.xml\n\n"
 281                               "Calculate and save the difference between the two files:\n\n"
 282                               "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
 283                               "Apply the patch to the original file:\n\n"
 284                               "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
 285                               "Apply the patch to the running cluster:\n\n"
 286                               "\t# cibadmin --patch -x patch.xml\n";
 287 
 288     context = pcmk__build_arg_context(args, NULL, NULL, NULL);
 289     g_option_context_set_description(context, description);
 290 
 291     pcmk__add_arg_group(context, "xml", "Original XML:",
 292                         "Show original XML options", original_xml_entries);
 293     pcmk__add_arg_group(context, "operation", "Operation:",
 294                         "Show operation options", operation_entries);
 295     pcmk__add_arg_group(context, "additional", "Additional Options:",
 296                         "Show additional options", addl_entries);
 297     return context;
 298 }
 299 
 300 int
 301 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 302 {
 303     int rc = pcmk_ok;
 304     xmlNode *object_1 = NULL;
 305     xmlNode *object_2 = NULL;
 306 
 307     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 308 
 309     GError *error = NULL;
 310     GOptionContext *context = NULL;
 311     gchar **processed_args = NULL;
 312 
 313     context = build_arg_context(args);
 314 
 315     crm_log_cli_init("crm_diff");
 316 
 317     processed_args = pcmk__cmdline_preproc(argv, "nopNO");
 318 
 319     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
 320         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
 321         rc = CRM_EX_USAGE;
 322         goto done;
 323     }
 324 
 325     for (int i = 0; i < args->verbosity; i++) {
 326         crm_bump_log_level(argc, argv);
 327     }
 328 
 329     if (args->version) {
 330         /* FIXME:  When crm_diff is converted to use formatted output, this can go. */
 331         pcmk__cli_help('v', CRM_EX_USAGE);
 332     }
 333 
 334     if (optind > argc) {
 335         char *help = g_option_context_get_help(context, TRUE, NULL);
 336 
 337         fprintf(stderr, "%s", help);
 338         g_free(help);
 339         rc = CRM_EX_USAGE;
 340         goto done;
 341     }
 342 
 343     if (options.apply && options.no_version) {
 344         fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
 345     } else if (options.as_cib && options.no_version) {
 346         fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
 347         rc = CRM_EX_USAGE;
 348         goto done;
 349     }
 350 
 351     if (options.raw_1) {
 352         object_1 = string2xml(options.xml_file_1);
 353 
 354     } else if (options.use_stdin) {
 355         fprintf(stderr, "Input first XML fragment:");
 356         object_1 = stdin2xml();
 357 
 358     } else if (options.xml_file_1 != NULL) {
 359         object_1 = filename2xml(options.xml_file_1);
 360     }
 361 
 362     if (options.raw_2) {
 363         object_2 = string2xml(options.xml_file_2);
 364 
 365     } else if (options.use_stdin) {
 366         fprintf(stderr, "Input second XML fragment:");
 367         object_2 = stdin2xml();
 368 
 369     } else if (options.xml_file_2 != NULL) {
 370         object_2 = filename2xml(options.xml_file_2);
 371     }
 372 
 373     if (object_1 == NULL) {
 374         fprintf(stderr, "Could not parse the first XML fragment\n");
 375         rc = CRM_EX_DATAERR;
 376         goto done;
 377     }
 378     if (object_2 == NULL) {
 379         fprintf(stderr, "Could not parse the second XML fragment\n");
 380         rc = CRM_EX_DATAERR;
 381         goto done;
 382     }
 383 
 384     if (options.apply) {
 385         rc = apply_patch(object_1, object_2, options.as_cib);
 386     } else {
 387         rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
 388     }
 389 
 390 done:
 391     g_strfreev(processed_args);
 392     g_clear_error(&error);
 393     pcmk__free_arg_context(context);
 394     free(options.xml_file_1);
 395     free(options.xml_file_2);
 396     free_xml(object_1);
 397     free_xml(object_2);
 398     return crm_errno2exit(rc);
 399 }

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