This source file includes following definitions.
- new_string_cb
- original_string_cb
- patch_cb
- print_patch
- apply_patch
- log_patch_cib_versions
- strip_patch_cib_version
- generate_patch
- build_arg_context
- main
1
2
3
4
5
6
7
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) {
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) {
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) {
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)
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
117 static int
118 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
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)
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)
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
207 static int
208 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
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
228
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;
254 }
255
256
257
258
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) {
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)
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
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 }