This source file includes following definitions.
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- set_danger_error
- get_instance_from_env
- check_file_exists
- connect_real_cib
- query_real_cib
- read_xml
- write_shadow_file
- get_shadow_prompt
- shadow_setup
- shadow_teardown
- commit_shadow_file
- create_shadow_empty
- create_shadow_from_cib
- delete_shadow_file
- edit_shadow_file
- show_shadow_contents
- show_shadow_diff
- show_shadow_filename
- show_shadow_instance
- switch_shadow_instance
- command_cb
- 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
15 #include <sys/param.h>
16 #include <crm/crm.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <fcntl.h>
23
24 #include <crm/common/cmdline_internal.h>
25 #include <crm/common/ipc.h>
26 #include <crm/common/output_internal.h>
27 #include <crm/common/xml.h>
28
29 #include <crm/cib.h>
30 #include <crm/cib/internal.h>
31
32 #define SUMMARY "perform Pacemaker configuration changes in a sandbox\n\n" \
33 "This command sets up an environment in which " \
34 "configuration tools (cibadmin,\n" \
35 "crm_resource, etc.) work offline instead of against a " \
36 "live cluster, allowing\n" \
37 "changes to be previewed and tested for side effects."
38
39 #define INDENT " "
40
41 enum shadow_command {
42 shadow_cmd_none = 0,
43 shadow_cmd_which,
44 shadow_cmd_display,
45 shadow_cmd_diff,
46 shadow_cmd_file,
47 shadow_cmd_create,
48 shadow_cmd_create_empty,
49 shadow_cmd_commit,
50 shadow_cmd_delete,
51 shadow_cmd_edit,
52 shadow_cmd_reset,
53 shadow_cmd_switch,
54 };
55
56
57
58
59
60
61
62
63 enum shadow_disp_flags {
64 shadow_disp_instance = (1 << 0),
65 shadow_disp_file = (1 << 1),
66 shadow_disp_content = (1 << 2),
67 shadow_disp_diff = (1 << 3),
68 };
69
70 static crm_exit_t exit_code = CRM_EX_OK;
71
72 static struct {
73 enum shadow_command cmd;
74 int cmd_options;
75 char *instance;
76 gboolean force;
77 gboolean batch;
78 gboolean full_upload;
79 gchar *validate_with;
80 } options = {
81 .cmd_options = cib_sync_call,
82 };
83
84
85
86
87
88
89
90
91
92
93
94
95
96 PCMK__OUTPUT_ARGS("instruction", "const char *")
97 static int
98 instruction_default(pcmk__output_t *out, va_list args)
99 {
100 const char *msg = va_arg(args, const char *);
101
102 if (msg == NULL) {
103 return pcmk_rc_no_output;
104 }
105 return out->info(out, "%s", msg);
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120 PCMK__OUTPUT_ARGS("instruction", "const char *")
121 static int
122 instruction_xml(pcmk__output_t *out, va_list args)
123 {
124 const char *msg = va_arg(args, const char *);
125
126 if (msg == NULL) {
127 return pcmk_rc_no_output;
128 }
129 pcmk__output_create_xml_text_node(out, "instruction", msg);
130 return pcmk_rc_ok;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
150 "const xmlNode *", "enum shadow_disp_flags")
151 static int
152 shadow_default(pcmk__output_t *out, va_list args)
153 {
154 const char *instance = va_arg(args, const char *);
155 const char *filename = va_arg(args, const char *);
156 const xmlNode *content = va_arg(args, const xmlNode *);
157 const xmlNode *diff = va_arg(args, const xmlNode *);
158 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
159
160 int rc = pcmk_rc_no_output;
161
162 if (pcmk_is_set(flags, shadow_disp_instance)) {
163 rc = out->info(out, "Instance: %s", pcmk__s(instance, "<unknown>"));
164 }
165 if (pcmk_is_set(flags, shadow_disp_file)) {
166 rc = out->info(out, "File name: %s", pcmk__s(filename, "<unknown>"));
167 }
168 if (pcmk_is_set(flags, shadow_disp_content)) {
169 rc = out->info(out, "Content:");
170
171 if (content != NULL) {
172 GString *buf = g_string_sized_new(1024);
173 gchar *str = NULL;
174
175 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
176 buf, 0);
177
178 str = g_string_free(buf, FALSE);
179 str = pcmk__trim(str);
180 if (!pcmk__str_empty(str)) {
181 out->info(out, "%s", str);
182 }
183 g_free(str);
184
185 } else {
186 out->info(out, "<unknown>");
187 }
188 }
189 if (pcmk_is_set(flags, shadow_disp_diff)) {
190 rc = out->info(out, "Diff:");
191
192 if (diff != NULL) {
193 out->message(out, "xml-patchset", diff);
194 } else {
195 out->info(out, "<empty>");
196 }
197 }
198
199 return rc;
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
219 "const xmlNode *", "enum shadow_disp_flags")
220 static int
221 shadow_text(pcmk__output_t *out, va_list args)
222 {
223 if (!out->is_quiet(out)) {
224 return shadow_default(out, args);
225
226 } else {
227 const char *instance = va_arg(args, const char *);
228 const char *filename = va_arg(args, const char *);
229 const xmlNode *content = va_arg(args, const xmlNode *);
230 const xmlNode *diff = va_arg(args, const xmlNode *);
231 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
232
233 int rc = pcmk_rc_no_output;
234 bool quiet_orig = out->quiet;
235
236
237
238
239 out->quiet = false;
240
241 if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) {
242 rc = out->info(out, "%s", instance);
243 }
244 if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) {
245 rc = out->info(out, "%s", filename);
246 }
247 if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) {
248 GString *buf = g_string_sized_new(1024);
249 gchar *str = NULL;
250
251 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
252 buf, 0);
253
254 str = g_string_free(buf, FALSE);
255 str = pcmk__trim(str);
256 rc = out->info(out, "%s", str);
257 g_free(str);
258 }
259 if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) {
260 rc = out->message(out, "xml-patchset", diff);
261 }
262
263 out->quiet = quiet_orig;
264 return rc;
265 }
266 }
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
286 "const xmlNode *", "enum shadow_disp_flags")
287 static int
288 shadow_xml(pcmk__output_t *out, va_list args)
289 {
290 const char *instance = va_arg(args, const char *);
291 const char *filename = va_arg(args, const char *);
292 const xmlNode *content = va_arg(args, const xmlNode *);
293 const xmlNode *diff = va_arg(args, const xmlNode *);
294 enum shadow_disp_flags flags G_GNUC_UNUSED =
295 (enum shadow_disp_flags) va_arg(args, int);
296
297 pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW,
298 PCMK_XA_INSTANCE, instance,
299 PCMK_XA_FILE, filename,
300 NULL);
301
302 if (content != NULL) {
303 GString *buf = g_string_sized_new(1024);
304
305 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
306 0);
307
308 out->output_xml(out, PCMK_XE_CONTENT, buf->str);
309 g_string_free(buf, TRUE);
310 }
311
312 if (diff != NULL) {
313 out->message(out, "xml-patchset", diff);
314 }
315
316 pcmk__output_xml_pop_parent(out);
317 return pcmk_rc_ok;
318 }
319
320 static const pcmk__supported_format_t formats[] = {
321 PCMK__SUPPORTED_FORMAT_NONE,
322 PCMK__SUPPORTED_FORMAT_TEXT,
323 PCMK__SUPPORTED_FORMAT_XML,
324 { NULL, NULL, NULL }
325 };
326
327 static const pcmk__message_entry_t fmt_functions[] = {
328 { "instruction", "default", instruction_default },
329 { "instruction", "xml", instruction_xml },
330 { "shadow", "default", shadow_default },
331 { "shadow", "text", shadow_text },
332 { "shadow", "xml", shadow_xml },
333
334 { NULL, NULL, NULL }
335 };
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 static void
351 set_danger_error(const char *reason, bool for_shadow, bool show_mismatch,
352 GError **error)
353 {
354 const char *active = getenv("CIB_shadow");
355 char *full = NULL;
356
357 if (show_mismatch
358 && !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) {
359
360 full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow "
361 "instance (%s) is not the same as the active "
362 "one (%s)",
363 reason, options.instance, active);
364 reason = full;
365 }
366
367 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
368 "%s%sTo prevent accidental destruction of the %s, the --force "
369 "flag is required in order to proceed.",
370 pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""),
371 (for_shadow? "shadow file" : "cluster"));
372 free(full);
373 }
374
375
376
377
378
379
380
381
382
383 static int
384 get_instance_from_env(GError **error)
385 {
386 int rc = pcmk_rc_ok;
387
388 pcmk__str_update(&options.instance, getenv("CIB_shadow"));
389 if (options.instance == NULL) {
390 rc = ENXIO;
391 exit_code = pcmk_rc2exitc(rc);
392 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
393 "No active shadow configuration defined");
394 }
395 return rc;
396 }
397
398
399
400
401
402
403
404
405
406
407
408 static int
409 check_file_exists(const char *filename, bool should_exist, GError **error)
410 {
411 struct stat buf;
412
413 if (!should_exist && (stat(filename, &buf) == 0)) {
414 char *reason = crm_strdup_printf("A shadow instance '%s' already "
415 "exists", options.instance);
416
417 exit_code = CRM_EX_CANTCREAT;
418 set_danger_error(reason, true, false, error);
419 free(reason);
420 return EEXIST;
421 }
422
423 if (should_exist && (stat(filename, &buf) < 0)) {
424
425 exit_code = CRM_EX_NOSUCH;
426 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
427 "Could not access shadow instance '%s': %s",
428 options.instance, strerror(errno));
429 return errno;
430 }
431
432 return pcmk_rc_ok;
433 }
434
435
436
437
438
439
440
441
442
443
444 static int
445 connect_real_cib(cib_t **real_cib, GError **error)
446 {
447 int rc = pcmk_rc_ok;
448
449 *real_cib = cib_new_no_shadow();
450 if (*real_cib == NULL) {
451 rc = ENOMEM;
452 exit_code = pcmk_rc2exitc(rc);
453 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
454 "Could not create a CIB connection object");
455 return rc;
456 }
457
458 rc = cib__signon_attempts(*real_cib, crm_system_name, cib_command, 5);
459 rc = pcmk_legacy2rc(rc);
460 if (rc != pcmk_rc_ok) {
461 exit_code = pcmk_rc2exitc(rc);
462 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
463 "Could not connect to CIB: %s", pcmk_rc_str(rc));
464 }
465 return rc;
466 }
467
468
469
470
471
472
473
474
475
476
477 static int
478 query_real_cib(xmlNode **output, GError **error)
479 {
480 cib_t *real_cib = NULL;
481 int rc = connect_real_cib(&real_cib, error);
482
483 if (rc != pcmk_rc_ok) {
484 goto done;
485 }
486
487 rc = real_cib->cmds->query(real_cib, NULL, output, options.cmd_options);
488 rc = pcmk_legacy2rc(rc);
489 if (rc != pcmk_rc_ok) {
490 exit_code = pcmk_rc2exitc(rc);
491 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
492 "Could not query the non-shadow CIB: %s", pcmk_rc_str(rc));
493 }
494
495 done:
496 cib_delete(real_cib);
497 return rc;
498 }
499
500
501
502
503
504
505
506
507
508
509
510 static int
511 read_xml(const char *filename, xmlNode **output, GError **error)
512 {
513 int rc = pcmk_rc_ok;
514
515 *output = pcmk__xml_read(filename);
516 if (*output == NULL) {
517 rc = pcmk_rc_no_input;
518 exit_code = pcmk_rc2exitc(rc);
519 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
520 "Could not parse XML from input file '%s'", filename);
521 }
522 return rc;
523 }
524
525
526
527
528
529
530
531
532
533
534 static int
535 write_shadow_file(const xmlNode *xml, const char *filename, bool reset,
536 GError **error)
537 {
538 int rc = pcmk__xml_write_file(xml, filename, false, NULL);
539
540 if (rc != pcmk_rc_ok) {
541 exit_code = pcmk_rc2exitc(rc);
542 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
543 "Could not %s the shadow instance '%s': %s",
544 reset? "reset" : "create", options.instance,
545 pcmk_rc_str(rc));
546 }
547 return rc;
548 }
549
550
551
552
553
554
555
556
557
558 static inline char *
559 get_shadow_prompt(void)
560 {
561 return crm_strdup_printf("shadow[%.40s] # ", options.instance);
562 }
563
564
565
566
567
568
569
570
571
572
573 static void
574 shadow_setup(pcmk__output_t *out, bool do_switch, GError **error)
575 {
576 const char *active = getenv("CIB_shadow");
577 const char *prompt = getenv("PS1");
578 const char *shell = getenv("SHELL");
579 char *new_prompt = get_shadow_prompt();
580
581 if (pcmk__str_eq(active, options.instance, pcmk__str_none)
582 && pcmk__str_eq(new_prompt, prompt, pcmk__str_none)) {
583
584 goto done;
585 }
586
587 if (!options.batch && (shell != NULL)) {
588 out->info(out, "Setting up shadow instance");
589 setenv("PS1", new_prompt, 1);
590 setenv("CIB_shadow", options.instance, 1);
591
592 out->message(out, PCMK_XE_INSTRUCTION,
593 "Press Ctrl+D to exit the crm_shadow shell");
594
595 if (pcmk__str_eq(shell, "(^|/)bash$", pcmk__str_regex)) {
596 execl(shell, shell, "--norc", "--noprofile", NULL);
597 } else {
598 execl(shell, shell, NULL);
599 }
600
601 exit_code = pcmk_rc2exitc(errno);
602 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
603 "Failed to launch shell '%s': %s",
604 shell, pcmk_rc_str(errno));
605
606 } else {
607 char *msg = NULL;
608 const char *prefix = "A new shadow instance was created. To begin "
609 "using it";
610
611 if (do_switch) {
612 prefix = "To switch to the named shadow instance";
613 }
614
615 msg = crm_strdup_printf("%s, enter the following into your shell:\n"
616 "\texport CIB_shadow=%s",
617 prefix, options.instance);
618 out->message(out, "instruction", msg);
619 free(msg);
620 }
621
622 done:
623 free(new_prompt);
624 }
625
626
627
628
629
630
631
632 static void
633 shadow_teardown(pcmk__output_t *out)
634 {
635 const char *active = getenv("CIB_shadow");
636 const char *prompt = getenv("PS1");
637
638 if (pcmk__str_eq(active, options.instance, pcmk__str_none)) {
639 char *our_prompt = get_shadow_prompt();
640
641 if (pcmk__str_eq(prompt, our_prompt, pcmk__str_none)) {
642 out->message(out, "instruction",
643 "Press Ctrl+D to exit the crm_shadow shell");
644
645 } else {
646 out->message(out, "instruction",
647 "Remember to unset the CIB_shadow variable by "
648 "entering the following into your shell:\n"
649 "\tunset CIB_shadow");
650 }
651 free(our_prompt);
652 }
653 }
654
655
656
657
658
659
660
661 static void
662 commit_shadow_file(GError **error)
663 {
664 char *filename = NULL;
665 cib_t *real_cib = NULL;
666
667 xmlNodePtr input = NULL;
668 xmlNodePtr section_xml = NULL;
669 const char *section = NULL;
670
671 int rc = pcmk_rc_ok;
672
673 if (!options.force) {
674 const char *reason = "The commit command overwrites the active cluster "
675 "configuration";
676
677 exit_code = CRM_EX_USAGE;
678 set_danger_error(reason, false, true, error);
679 return;
680 }
681
682 filename = get_shadow_file(options.instance);
683 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
684 goto done;
685 }
686
687 if (connect_real_cib(&real_cib, error) != pcmk_rc_ok) {
688 goto done;
689 }
690
691 if (read_xml(filename, &input, error) != pcmk_rc_ok) {
692 goto done;
693 }
694
695 section_xml = input;
696
697 if (!options.full_upload) {
698 section = PCMK_XE_CONFIGURATION;
699 section_xml = pcmk__xe_first_child(input, section, NULL, NULL);
700 }
701
702 rc = real_cib->cmds->replace(real_cib, section, section_xml,
703 options.cmd_options);
704 rc = pcmk_legacy2rc(rc);
705
706 if (rc != pcmk_rc_ok) {
707 exit_code = pcmk_rc2exitc(rc);
708 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
709 "Could not commit shadow instance '%s' to the CIB: %s",
710 options.instance, pcmk_rc_str(rc));
711 }
712
713 done:
714 free(filename);
715 cib_delete(real_cib);
716 free_xml(input);
717 }
718
719
720
721
722
723
724
725
726
727
728
729 static void
730 create_shadow_empty(pcmk__output_t *out, GError **error)
731 {
732 char *filename = get_shadow_file(options.instance);
733 xmlNode *output = NULL;
734
735 if (!options.force
736 && (check_file_exists(filename, false, error) != pcmk_rc_ok)) {
737 goto done;
738 }
739
740 output = createEmptyCib(0);
741 crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
742 out->info(out, "Created new %s configuration",
743 crm_element_value(output, PCMK_XA_VALIDATE_WITH));
744
745 if (write_shadow_file(output, filename, false, error) != pcmk_rc_ok) {
746 goto done;
747 }
748 shadow_setup(out, false, error);
749
750 done:
751 free(filename);
752 free_xml(output);
753 }
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768 static void
769 create_shadow_from_cib(pcmk__output_t *out, bool reset, GError **error)
770 {
771 char *filename = get_shadow_file(options.instance);
772 xmlNode *output = NULL;
773
774 if (!options.force) {
775 if (reset) {
776
777
778
779
780
781 const char *local = getenv("CIB_shadow");
782
783 if (!pcmk__str_eq(local, options.instance, pcmk__str_null_matches)) {
784 exit_code = CRM_EX_USAGE;
785 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
786 "The supplied shadow instance (%s) is not the same "
787 "as the active one (%s).\n"
788 "To prevent accidental destruction of the shadow "
789 "file, the --force flag is required in order to "
790 "proceed.",
791 options.instance, local);
792 goto done;
793 }
794 }
795
796 if (check_file_exists(filename, reset, error) != pcmk_rc_ok) {
797 goto done;
798 }
799 }
800
801 if (query_real_cib(&output, error) != pcmk_rc_ok) {
802 goto done;
803 }
804
805 if (write_shadow_file(output, filename, reset, error) != pcmk_rc_ok) {
806 goto done;
807 }
808 shadow_setup(out, false, error);
809
810 done:
811 free(filename);
812 free_xml(output);
813 }
814
815
816
817
818
819
820
821
822 static void
823 delete_shadow_file(pcmk__output_t *out, GError **error)
824 {
825 char *filename = NULL;
826
827 if (!options.force) {
828 const char *reason = "The delete command removes the specified shadow "
829 "file";
830
831 exit_code = CRM_EX_USAGE;
832 set_danger_error(reason, true, true, error);
833 return;
834 }
835
836 filename = get_shadow_file(options.instance);
837
838 if ((unlink(filename) < 0) && (errno != ENOENT)) {
839 exit_code = pcmk_rc2exitc(errno);
840 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
841 "Could not remove shadow instance '%s': %s",
842 options.instance, strerror(errno));
843 } else {
844 shadow_teardown(out);
845 }
846 free(filename);
847 }
848
849
850
851
852
853
854
855
856
857 static void
858 edit_shadow_file(GError **error)
859 {
860 char *filename = NULL;
861 const char *editor = NULL;
862
863 if (get_instance_from_env(error) != pcmk_rc_ok) {
864 return;
865 }
866
867 filename = get_shadow_file(options.instance);
868 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
869 goto done;
870 }
871
872 editor = getenv("EDITOR");
873 if (editor == NULL) {
874 exit_code = CRM_EX_NOT_CONFIGURED;
875 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
876 "No value for EDITOR defined");
877 goto done;
878 }
879
880 execlp(editor, "--", filename, NULL);
881 exit_code = CRM_EX_OSFILE;
882 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
883 "Could not invoke EDITOR (%s %s): %s",
884 editor, filename, strerror(errno));
885
886 done:
887 free(filename);
888 }
889
890
891
892
893
894
895
896
897 static void
898 show_shadow_contents(pcmk__output_t *out, GError **error)
899 {
900 char *filename = NULL;
901
902 if (get_instance_from_env(error) != pcmk_rc_ok) {
903 return;
904 }
905
906 filename = get_shadow_file(options.instance);
907
908 if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
909 xmlNode *output = NULL;
910 bool quiet_orig = out->quiet;
911
912 if (read_xml(filename, &output, error) != pcmk_rc_ok) {
913 goto done;
914 }
915
916 out->quiet = true;
917 out->message(out, "shadow",
918 options.instance, NULL, output, NULL, shadow_disp_content);
919 out->quiet = quiet_orig;
920
921 free_xml(output);
922 }
923
924 done:
925 free(filename);
926 }
927
928
929
930
931
932
933
934
935 static void
936 show_shadow_diff(pcmk__output_t *out, GError **error)
937 {
938 char *filename = NULL;
939 xmlNodePtr old_config = NULL;
940 xmlNodePtr new_config = NULL;
941 xmlNodePtr diff = NULL;
942 bool quiet_orig = out->quiet;
943
944 if (get_instance_from_env(error) != pcmk_rc_ok) {
945 return;
946 }
947
948 filename = get_shadow_file(options.instance);
949 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
950 goto done;
951 }
952
953 if (query_real_cib(&old_config, error) != pcmk_rc_ok) {
954 goto done;
955 }
956
957 if (read_xml(filename, &new_config, error) != pcmk_rc_ok) {
958 goto done;
959 }
960 xml_track_changes(new_config, NULL, new_config, false);
961 xml_calculate_changes(old_config, new_config);
962 diff = xml_create_patchset(0, old_config, new_config, NULL, false);
963
964 pcmk__log_xml_changes(LOG_INFO, new_config);
965 xml_accept_changes(new_config);
966
967 out->quiet = true;
968 out->message(out, "shadow",
969 options.instance, NULL, NULL, diff, shadow_disp_diff);
970 out->quiet = quiet_orig;
971
972 if (diff != NULL) {
973
974
975
976
977 exit_code = CRM_EX_ERROR;
978 }
979
980 done:
981 free(filename);
982 free_xml(old_config);
983 free_xml(new_config);
984 free_xml(diff);
985 }
986
987
988
989
990
991
992
993
994 static void
995 show_shadow_filename(pcmk__output_t *out, GError **error)
996 {
997 if (get_instance_from_env(error) == pcmk_rc_ok) {
998 char *filename = get_shadow_file(options.instance);
999 bool quiet_orig = out->quiet;
1000
1001 out->quiet = true;
1002 out->message(out, "shadow",
1003 options.instance, filename, NULL, NULL, shadow_disp_file);
1004 out->quiet = quiet_orig;
1005
1006 free(filename);
1007 }
1008 }
1009
1010
1011
1012
1013
1014
1015
1016
1017 static void
1018 show_shadow_instance(pcmk__output_t *out, GError **error)
1019 {
1020 if (get_instance_from_env(error) == pcmk_rc_ok) {
1021 bool quiet_orig = out->quiet;
1022
1023 out->quiet = true;
1024 out->message(out, "shadow",
1025 options.instance, NULL, NULL, NULL, shadow_disp_instance);
1026 out->quiet = quiet_orig;
1027 }
1028 }
1029
1030
1031
1032
1033
1034
1035
1036
1037 static void
1038 switch_shadow_instance(pcmk__output_t *out, GError **error)
1039 {
1040 char *filename = NULL;
1041
1042 filename = get_shadow_file(options.instance);
1043 if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
1044 shadow_setup(out, true, error);
1045 }
1046 free(filename);
1047 }
1048
1049 static gboolean
1050 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
1051 GError **error)
1052 {
1053 if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) {
1054 options.cmd = shadow_cmd_which;
1055
1056 } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) {
1057 options.cmd = shadow_cmd_display;
1058
1059 } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) {
1060 options.cmd = shadow_cmd_diff;
1061
1062 } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) {
1063 options.cmd = shadow_cmd_file;
1064
1065 } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) {
1066 options.cmd = shadow_cmd_create;
1067
1068 } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) {
1069 options.cmd = shadow_cmd_create_empty;
1070
1071 } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) {
1072 options.cmd = shadow_cmd_commit;
1073
1074 } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
1075 options.cmd = shadow_cmd_delete;
1076
1077 } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) {
1078 options.cmd = shadow_cmd_edit;
1079
1080 } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) {
1081 options.cmd = shadow_cmd_reset;
1082
1083 } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) {
1084 options.cmd = shadow_cmd_switch;
1085
1086 } else {
1087
1088 return FALSE;
1089 }
1090
1091
1092 pcmk__str_update(&options.instance, optarg);
1093 return TRUE;
1094 }
1095
1096 static GOptionEntry query_entries[] = {
1097 { "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1098 "Indicate the active shadow copy", NULL },
1099
1100 { "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1101 "Display the contents of the active shadow copy", NULL },
1102
1103 { "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1104 "Display the changes in the active shadow copy", NULL },
1105
1106 { "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1107 "Display the location of the active shadow copy file", NULL },
1108
1109 { NULL }
1110 };
1111
1112 static GOptionEntry command_entries[] = {
1113 { "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1114 "Create the named shadow copy of the active cluster configuration",
1115 "name" },
1116
1117 { "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
1118 command_cb,
1119 "Create the named shadow copy with an empty cluster configuration.\n"
1120 INDENT "Optional: --validate-with", "name" },
1121
1122 { "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1123 "Upload the contents of the named shadow copy to the cluster", "name" },
1124
1125 { "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1126 "Delete the contents of the named shadow copy", "name" },
1127
1128 { "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1129 "Edit the contents of the active shadow copy with your favorite $EDITOR",
1130 NULL },
1131
1132 { "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1133 "Recreate named shadow copy from the active cluster configuration",
1134 "name" },
1135
1136 { "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1137 "(Advanced) Switch to the named shadow copy", "name" },
1138
1139 { NULL }
1140 };
1141
1142 static GOptionEntry addl_entries[] = {
1143 { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
1144 "(Advanced) Force the action to be performed", NULL },
1145
1146 { "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch,
1147 "(Advanced) Don't spawn a new shell", NULL },
1148
1149 { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload,
1150 "(Advanced) Upload entire CIB, including status, with --commit", NULL },
1151
1152 { "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
1153 &options.validate_with,
1154 "(Advanced) Create an older configuration version", NULL },
1155
1156 { NULL }
1157 };
1158
1159 static GOptionContext *
1160 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
1161 {
1162 const char *desc = NULL;
1163 GOptionContext *context = NULL;
1164
1165 desc = "Examples:\n\n"
1166 "Create a blank shadow configuration:\n\n"
1167 "\t# crm_shadow --create-empty myShadow\n\n"
1168 "Create a shadow configuration from the running cluster\n\n"
1169 "\t# crm_shadow --create myShadow\n\n"
1170 "Display the current shadow configuration:\n\n"
1171 "\t# crm_shadow --display\n\n"
1172 "Discard the current shadow configuration (named myShadow):\n\n"
1173 "\t# crm_shadow --delete myShadow --force\n\n"
1174 "Upload current shadow configuration (named myShadow) to running "
1175 "cluster:\n\n"
1176 "\t# crm_shadow --commit myShadow\n\n";
1177
1178 context = pcmk__build_arg_context(args, "text (default), xml", group,
1179 "<query>|<command>");
1180 g_option_context_set_description(context, desc);
1181
1182 pcmk__add_arg_group(context, "queries", "Queries:",
1183 "Show query help", query_entries);
1184 pcmk__add_arg_group(context, "commands", "Commands:",
1185 "Show command help", command_entries);
1186 pcmk__add_arg_group(context, "additional", "Additional Options:",
1187 "Show additional options", addl_entries);
1188 return context;
1189 }
1190
1191 int
1192 main(int argc, char **argv)
1193 {
1194 int rc = pcmk_rc_ok;
1195 pcmk__output_t *out = NULL;
1196
1197 GError *error = NULL;
1198
1199 GOptionGroup *output_group = NULL;
1200 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1201 gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv");
1202 GOptionContext *context = build_arg_context(args, &output_group);
1203
1204 crm_log_preinit(NULL, argc, argv);
1205
1206 pcmk__register_formats(output_group, formats);
1207
1208 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1209 exit_code = CRM_EX_USAGE;
1210 goto done;
1211 }
1212
1213 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1214 if (rc != pcmk_rc_ok) {
1215 exit_code = CRM_EX_ERROR;
1216 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1217 "Error creating output format %s: %s", args->output_ty,
1218 pcmk_rc_str(rc));
1219 goto done;
1220 }
1221
1222 if (g_strv_length(processed_args) > 1) {
1223 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1224 GString *extra = g_string_sized_new(128);
1225
1226 for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
1227 if (extra->len > 0) {
1228 g_string_append_c(extra, ' ');
1229 }
1230 g_string_append(extra, processed_args[lpc]);
1231 }
1232
1233 exit_code = CRM_EX_USAGE;
1234 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1235 "non-option ARGV-elements: %s\n\n%s", extra->str, help);
1236 g_free(help);
1237 g_string_free(extra, TRUE);
1238 goto done;
1239 }
1240
1241 if (args->version) {
1242 out->version(out, false);
1243 goto done;
1244 }
1245
1246 pcmk__register_messages(out, fmt_functions);
1247
1248 if (options.cmd == shadow_cmd_none) {
1249
1250 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1251
1252 exit_code = CRM_EX_USAGE;
1253 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1254 "Must specify a query or command option\n\n%s", help);
1255 g_free(help);
1256 goto done;
1257 }
1258
1259 pcmk__cli_init_logging("crm_shadow", args->verbosity);
1260
1261 if (args->verbosity > 0) {
1262 cib__set_call_options(options.cmd_options, crm_system_name,
1263 cib_verbose);
1264 }
1265
1266
1267 switch (options.cmd) {
1268 case shadow_cmd_commit:
1269 commit_shadow_file(&error);
1270 break;
1271 case shadow_cmd_create:
1272 create_shadow_from_cib(out, false, &error);
1273 break;
1274 case shadow_cmd_create_empty:
1275 create_shadow_empty(out, &error);
1276 break;
1277 case shadow_cmd_reset:
1278 create_shadow_from_cib(out, true, &error);
1279 break;
1280 case shadow_cmd_delete:
1281 delete_shadow_file(out, &error);
1282 break;
1283 case shadow_cmd_diff:
1284 show_shadow_diff(out, &error);
1285 break;
1286 case shadow_cmd_display:
1287 show_shadow_contents(out, &error);
1288 break;
1289 case shadow_cmd_edit:
1290 edit_shadow_file(&error);
1291 break;
1292 case shadow_cmd_file:
1293 show_shadow_filename(out, &error);
1294 break;
1295 case shadow_cmd_switch:
1296 switch_shadow_instance(out, &error);
1297 break;
1298 case shadow_cmd_which:
1299 show_shadow_instance(out, &error);
1300 break;
1301 default:
1302
1303 break;
1304 }
1305
1306 done:
1307 g_strfreev(processed_args);
1308 pcmk__free_arg_context(context);
1309
1310 pcmk__output_and_clear_error(&error, out);
1311
1312 free(options.instance);
1313 g_free(options.validate_with);
1314
1315 if (out != NULL) {
1316 out->finish(out, exit_code, true, NULL);
1317 pcmk__output_free(out);
1318 }
1319
1320 crm_exit(exit_code);
1321 }