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/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) {
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) {
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) {
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)
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
118 static int
119 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
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)
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)
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
209 static int
210 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
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
222
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;
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
262
263
264
265 return pcmk_rc_error;
266 }
267
268 static GOptionContext *
269 build_arg_context(pcmk__common_args_t *args) {
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)
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
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 }