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 *", "const xmlNode *",
151 "const xmlNode *", "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 const xmlNode *content = va_arg(args, const xmlNode *);
158 const xmlNode *diff = va_arg(args, const xmlNode *);
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 *", "const xmlNode *",
214 "const xmlNode *", "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 const xmlNode *content = va_arg(args, const xmlNode *);
225 const xmlNode *diff = va_arg(args, const xmlNode *);
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 *", "const xmlNode *",
275 "const xmlNode *", "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 const xmlNode *content = va_arg(args, const xmlNode *);
282 const xmlNode *diff = va_arg(args, const xmlNode *);
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(const 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 bool quiet_orig = out->quiet;
931
932 if (get_instance_from_env(error) != pcmk_rc_ok) {
933 return;
934 }
935
936 filename = get_shadow_file(options.instance);
937 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
938 goto done;
939 }
940
941 if (query_real_cib(&old_config, error) != pcmk_rc_ok) {
942 goto done;
943 }
944
945 if (read_xml(filename, &new_config, error) != pcmk_rc_ok) {
946 goto done;
947 }
948 xml_track_changes(new_config, NULL, new_config, false);
949 xml_calculate_changes(old_config, new_config);
950 diff = xml_create_patchset(0, old_config, new_config, NULL, false);
951
952 pcmk__log_xml_changes(LOG_INFO, new_config);
953 xml_accept_changes(new_config);
954
955 out->quiet = true;
956 out->message(out, "shadow",
957 options.instance, NULL, NULL, diff, shadow_disp_diff);
958 out->quiet = quiet_orig;
959
960 if (diff != NULL) {
961
962
963
964
965 exit_code = CRM_EX_ERROR;
966 }
967
968 done:
969 free(filename);
970 free_xml(old_config);
971 free_xml(new_config);
972 free_xml(diff);
973 }
974
975
976
977
978
979
980
981
982 static void
983 show_shadow_filename(pcmk__output_t *out, GError **error)
984 {
985 if (get_instance_from_env(error) == pcmk_rc_ok) {
986 char *filename = get_shadow_file(options.instance);
987 bool quiet_orig = out->quiet;
988
989 out->quiet = true;
990 out->message(out, "shadow",
991 options.instance, filename, NULL, NULL, shadow_disp_file);
992 out->quiet = quiet_orig;
993
994 free(filename);
995 }
996 }
997
998
999
1000
1001
1002
1003
1004
1005 static void
1006 show_shadow_instance(pcmk__output_t *out, GError **error)
1007 {
1008 if (get_instance_from_env(error) == pcmk_rc_ok) {
1009 bool quiet_orig = out->quiet;
1010
1011 out->quiet = true;
1012 out->message(out, "shadow",
1013 options.instance, NULL, NULL, NULL, shadow_disp_instance);
1014 out->quiet = quiet_orig;
1015 }
1016 }
1017
1018
1019
1020
1021
1022
1023
1024
1025 static void
1026 switch_shadow_instance(pcmk__output_t *out, GError **error)
1027 {
1028 char *filename = NULL;
1029
1030 filename = get_shadow_file(options.instance);
1031 if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
1032 shadow_setup(out, true, error);
1033 }
1034 free(filename);
1035 }
1036
1037 static gboolean
1038 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
1039 GError **error)
1040 {
1041 if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) {
1042 options.cmd = shadow_cmd_which;
1043
1044 } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) {
1045 options.cmd = shadow_cmd_display;
1046
1047 } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) {
1048 options.cmd = shadow_cmd_diff;
1049
1050 } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) {
1051 options.cmd = shadow_cmd_file;
1052
1053 } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) {
1054 options.cmd = shadow_cmd_create;
1055
1056 } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) {
1057 options.cmd = shadow_cmd_create_empty;
1058
1059 } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) {
1060 options.cmd = shadow_cmd_commit;
1061
1062 } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
1063 options.cmd = shadow_cmd_delete;
1064
1065 } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) {
1066 options.cmd = shadow_cmd_edit;
1067
1068 } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) {
1069 options.cmd = shadow_cmd_reset;
1070
1071 } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) {
1072 options.cmd = shadow_cmd_switch;
1073
1074 } else {
1075
1076 return FALSE;
1077 }
1078
1079
1080 pcmk__str_update(&options.instance, optarg);
1081 return TRUE;
1082 }
1083
1084 static GOptionEntry query_entries[] = {
1085 { "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1086 "Indicate the active shadow copy", NULL },
1087
1088 { "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1089 "Display the contents of the active shadow copy", NULL },
1090
1091 { "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1092 "Display the changes in the active shadow copy", NULL },
1093
1094 { "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1095 "Display the location of the active shadow copy file", NULL },
1096
1097 { NULL }
1098 };
1099
1100 static GOptionEntry command_entries[] = {
1101 { "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1102 "Create the named shadow copy of the active cluster configuration",
1103 "name" },
1104
1105 { "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
1106 command_cb,
1107 "Create the named shadow copy with an empty cluster configuration.\n"
1108 INDENT "Optional: --validate-with", "name" },
1109
1110 { "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1111 "Upload the contents of the named shadow copy to the cluster", "name" },
1112
1113 { "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1114 "Delete the contents of the named shadow copy", "name" },
1115
1116 { "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1117 "Edit the contents of the active shadow copy with your favorite $EDITOR",
1118 NULL },
1119
1120 { "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1121 "Recreate named shadow copy from the active cluster configuration",
1122 "name" },
1123
1124 { "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1125 "(Advanced) Switch to the named shadow copy", "name" },
1126
1127 { NULL }
1128 };
1129
1130 static GOptionEntry addl_entries[] = {
1131 { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
1132 "(Advanced) Force the action to be performed", NULL },
1133
1134 { "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch,
1135 "(Advanced) Don't spawn a new shell", NULL },
1136
1137 { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload,
1138 "(Advanced) Upload entire CIB, including status, with --commit", NULL },
1139
1140 { "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
1141 &options.validate_with,
1142 "(Advanced) Create an older configuration version", NULL },
1143
1144 { NULL }
1145 };
1146
1147 static GOptionContext *
1148 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
1149 {
1150 const char *desc = NULL;
1151 GOptionContext *context = NULL;
1152
1153 desc = "Examples:\n\n"
1154 "Create a blank shadow configuration:\n\n"
1155 "\t# crm_shadow --create-empty myShadow\n\n"
1156 "Create a shadow configuration from the running cluster\n\n"
1157 "\t# crm_shadow --create myShadow\n\n"
1158 "Display the current shadow configuration:\n\n"
1159 "\t# crm_shadow --display\n\n"
1160 "Discard the current shadow configuration (named myShadow):\n\n"
1161 "\t# crm_shadow --delete myShadow --force\n\n"
1162 "Upload current shadow configuration (named myShadow) to running "
1163 "cluster:\n\n"
1164 "\t# crm_shadow --commit myShadow\n\n";
1165
1166 context = pcmk__build_arg_context(args, "text (default), xml", group,
1167 "<query>|<command>");
1168 g_option_context_set_description(context, desc);
1169
1170 pcmk__add_arg_group(context, "queries", "Queries:",
1171 "Show query help", query_entries);
1172 pcmk__add_arg_group(context, "commands", "Commands:",
1173 "Show command help", command_entries);
1174 pcmk__add_arg_group(context, "additional", "Additional Options:",
1175 "Show additional options", addl_entries);
1176 return context;
1177 }
1178
1179 int
1180 main(int argc, char **argv)
1181 {
1182 int rc = pcmk_rc_ok;
1183 pcmk__output_t *out = NULL;
1184
1185 GError *error = NULL;
1186
1187 GOptionGroup *output_group = NULL;
1188 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1189 gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv");
1190 GOptionContext *context = build_arg_context(args, &output_group);
1191
1192 crm_log_preinit(NULL, argc, argv);
1193
1194 pcmk__register_formats(output_group, formats);
1195
1196 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1197 exit_code = CRM_EX_USAGE;
1198 goto done;
1199 }
1200
1201 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1202 if (rc != pcmk_rc_ok) {
1203 exit_code = CRM_EX_ERROR;
1204 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1205 "Error creating output format %s: %s", args->output_ty,
1206 pcmk_rc_str(rc));
1207 goto done;
1208 }
1209
1210 if (g_strv_length(processed_args) > 1) {
1211 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1212 GString *extra = g_string_sized_new(128);
1213
1214 for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
1215 if (extra->len > 0) {
1216 g_string_append_c(extra, ' ');
1217 }
1218 g_string_append(extra, processed_args[lpc]);
1219 }
1220
1221 exit_code = CRM_EX_USAGE;
1222 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1223 "non-option ARGV-elements: %s\n\n%s", extra->str, help);
1224 g_free(help);
1225 g_string_free(extra, TRUE);
1226 goto done;
1227 }
1228
1229 if (args->version) {
1230 out->version(out, false);
1231 goto done;
1232 }
1233
1234 pcmk__register_messages(out, fmt_functions);
1235
1236 if (options.cmd == shadow_cmd_none) {
1237
1238 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1239
1240 exit_code = CRM_EX_USAGE;
1241 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1242 "Must specify a query or command option\n\n%s", help);
1243 g_free(help);
1244 goto done;
1245 }
1246
1247 pcmk__cli_init_logging("crm_shadow", args->verbosity);
1248
1249 if (args->verbosity > 0) {
1250 cib__set_call_options(options.cmd_options, crm_system_name,
1251 cib_verbose);
1252 }
1253
1254
1255 switch (options.cmd) {
1256 case shadow_cmd_commit:
1257 commit_shadow_file(&error);
1258 break;
1259 case shadow_cmd_create:
1260 create_shadow_from_cib(out, false, &error);
1261 break;
1262 case shadow_cmd_create_empty:
1263 create_shadow_empty(out, &error);
1264 break;
1265 case shadow_cmd_reset:
1266 create_shadow_from_cib(out, true, &error);
1267 break;
1268 case shadow_cmd_delete:
1269 delete_shadow_file(out, &error);
1270 break;
1271 case shadow_cmd_diff:
1272 show_shadow_diff(out, &error);
1273 break;
1274 case shadow_cmd_display:
1275 show_shadow_contents(out, &error);
1276 break;
1277 case shadow_cmd_edit:
1278 edit_shadow_file(&error);
1279 break;
1280 case shadow_cmd_file:
1281 show_shadow_filename(out, &error);
1282 break;
1283 case shadow_cmd_switch:
1284 switch_shadow_instance(out, &error);
1285 break;
1286 case shadow_cmd_which:
1287 show_shadow_instance(out, &error);
1288 break;
1289 default:
1290
1291 break;
1292 }
1293
1294 done:
1295 g_strfreev(processed_args);
1296 pcmk__free_arg_context(context);
1297
1298 pcmk__output_and_clear_error(&error, out);
1299
1300 free(options.instance);
1301 g_free(options.validate_with);
1302
1303 if (out != NULL) {
1304 out->finish(out, exit_code, true, NULL);
1305 pcmk__output_free(out);
1306 }
1307
1308 crm_exit(exit_code);
1309 }