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