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 enum shadow_disp_flags {
63 shadow_disp_instance = (1 << 0),
64 shadow_disp_file = (1 << 1),
65 shadow_disp_content = (1 << 2),
66 shadow_disp_diff = (1 << 3),
67 };
68
69 static crm_exit_t exit_code = CRM_EX_OK;
70
71 static struct {
72 enum shadow_command cmd;
73 int cmd_options;
74 char *instance;
75 gboolean force;
76 gboolean batch;
77 gboolean full_upload;
78 gchar *validate_with;
79 } options = {
80 .cmd_options = cib_sync_call,
81 };
82
83
84
85
86
87
88
89
90
91
92
93
94
95 PCMK__OUTPUT_ARGS("instruction", "const char *")
96 static int
97 instruction_default(pcmk__output_t *out, va_list args)
98 {
99 const char *msg = va_arg(args, const char *);
100
101 if (msg == NULL) {
102 return pcmk_rc_no_output;
103 }
104 return out->info(out, "%s", msg);
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119 PCMK__OUTPUT_ARGS("instruction", "const char *")
120 static int
121 instruction_xml(pcmk__output_t *out, va_list args)
122 {
123 const char *msg = va_arg(args, const char *);
124
125 if (msg == NULL) {
126 return pcmk_rc_no_output;
127 }
128 pcmk__output_create_xml_text_node(out, "instruction", msg);
129 return pcmk_rc_ok;
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
149 "const xmlNode *", "enum shadow_disp_flags")
150 static int
151 shadow_default(pcmk__output_t *out, va_list args)
152 {
153 const char *instance = va_arg(args, const char *);
154 const char *filename = va_arg(args, const char *);
155 const xmlNode *content = va_arg(args, const xmlNode *);
156 const xmlNode *diff = va_arg(args, const xmlNode *);
157 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
158
159 int rc = pcmk_rc_no_output;
160
161 if (pcmk_is_set(flags, shadow_disp_instance)) {
162 rc = out->info(out, "Instance: %s", pcmk__s(instance, "<unknown>"));
163 }
164 if (pcmk_is_set(flags, shadow_disp_file)) {
165 rc = out->info(out, "File name: %s", pcmk__s(filename, "<unknown>"));
166 }
167 if (pcmk_is_set(flags, shadow_disp_content)) {
168 rc = out->info(out, "Content:");
169
170 if (content != NULL) {
171 GString *buf = g_string_sized_new(1024);
172 gchar *str = NULL;
173
174 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
175 buf, 0);
176
177 str = g_string_free(buf, FALSE);
178 str = pcmk__trim(str);
179 if (!pcmk__str_empty(str)) {
180 out->info(out, "%s", str);
181 }
182 g_free(str);
183
184 } else {
185 out->info(out, "<unknown>");
186 }
187 }
188 if (pcmk_is_set(flags, shadow_disp_diff)) {
189 rc = out->info(out, "Diff:");
190
191 if (diff != NULL) {
192 out->message(out, "xml-patchset", diff);
193 } else {
194 out->info(out, "<empty>");
195 }
196 }
197
198 return rc;
199 }
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
218 "const xmlNode *", "enum shadow_disp_flags")
219 static int
220 shadow_text(pcmk__output_t *out, va_list args)
221 {
222 if (!out->is_quiet(out)) {
223 return shadow_default(out, args);
224
225 } else {
226 const char *instance = va_arg(args, const char *);
227 const char *filename = va_arg(args, const char *);
228 const xmlNode *content = va_arg(args, const xmlNode *);
229 const xmlNode *diff = va_arg(args, const xmlNode *);
230 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
231
232 int rc = pcmk_rc_no_output;
233 bool quiet_orig = out->quiet;
234
235
236
237
238 out->quiet = false;
239
240 if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) {
241 rc = out->info(out, "%s", instance);
242 }
243 if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) {
244 rc = out->info(out, "%s", filename);
245 }
246 if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) {
247 GString *buf = g_string_sized_new(1024);
248 gchar *str = NULL;
249
250 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
251 buf, 0);
252
253 str = g_string_free(buf, FALSE);
254 str = pcmk__trim(str);
255 rc = out->info(out, "%s", str);
256 g_free(str);
257 }
258 if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) {
259 rc = out->message(out, "xml-patchset", diff);
260 }
261
262 out->quiet = quiet_orig;
263 return rc;
264 }
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
285 "const xmlNode *", "enum shadow_disp_flags")
286 static int
287 shadow_xml(pcmk__output_t *out, va_list args)
288 {
289 const char *instance = va_arg(args, const char *);
290 const char *filename = va_arg(args, const char *);
291 const xmlNode *content = va_arg(args, const xmlNode *);
292 const xmlNode *diff = va_arg(args, const xmlNode *);
293 enum shadow_disp_flags flags G_GNUC_UNUSED =
294 (enum shadow_disp_flags) va_arg(args, int);
295
296 pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW,
297 PCMK_XA_INSTANCE, instance,
298 PCMK_XA_FILE, filename,
299 NULL);
300
301 if (content != NULL) {
302 GString *buf = g_string_sized_new(1024);
303
304 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
305 0);
306
307 out->output_xml(out, PCMK_XE_CONTENT, buf->str);
308 g_string_free(buf, TRUE);
309 }
310
311 if (diff != NULL) {
312 out->message(out, "xml-patchset", diff);
313 }
314
315 pcmk__output_xml_pop_parent(out);
316 return pcmk_rc_ok;
317 }
318
319 static const pcmk__supported_format_t formats[] = {
320 PCMK__SUPPORTED_FORMAT_NONE,
321 PCMK__SUPPORTED_FORMAT_TEXT,
322 PCMK__SUPPORTED_FORMAT_XML,
323 { NULL, NULL, NULL }
324 };
325
326 static const pcmk__message_entry_t fmt_functions[] = {
327 { "instruction", "default", instruction_default },
328 { "instruction", "xml", instruction_xml },
329 { "shadow", "default", shadow_default },
330 { "shadow", "text", shadow_text },
331 { "shadow", "xml", shadow_xml },
332
333 { NULL, NULL, NULL }
334 };
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 static void
350 set_danger_error(const char *reason, bool for_shadow, bool show_mismatch,
351 GError **error)
352 {
353 const char *active = getenv("CIB_shadow");
354 char *full = NULL;
355
356 if (show_mismatch
357 && !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) {
358
359 full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow "
360 "instance (%s) is not the same as the active "
361 "one (%s)",
362 reason, options.instance, active);
363 reason = full;
364 }
365
366 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
367 "%s%sTo prevent accidental destruction of the %s, the --force "
368 "flag is required in order to proceed.",
369 pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""),
370 (for_shadow? "shadow file" : "cluster"));
371 free(full);
372 }
373
374
375
376
377
378
379
380
381
382 static int
383 get_instance_from_env(GError **error)
384 {
385 int rc = pcmk_rc_ok;
386
387 pcmk__str_update(&options.instance, getenv("CIB_shadow"));
388 if (options.instance == NULL) {
389 rc = ENXIO;
390 exit_code = pcmk_rc2exitc(rc);
391 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
392 "No active shadow configuration defined");
393 }
394 return rc;
395 }
396
397
398
399
400
401
402
403
404
405
406
407 static int
408 check_file_exists(const char *filename, bool should_exist, GError **error)
409 {
410 struct stat buf;
411
412 if (!should_exist && (stat(filename, &buf) == 0)) {
413 char *reason = crm_strdup_printf("A shadow instance '%s' already "
414 "exists", options.instance);
415
416 exit_code = CRM_EX_CANTCREAT;
417 set_danger_error(reason, true, false, error);
418 free(reason);
419 return EEXIST;
420 }
421
422 if (should_exist && (stat(filename, &buf) < 0)) {
423 int rc = errno;
424
425 exit_code = pcmk_rc2exitc(rc);
426 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
427 "Could not access shadow instance '%s': %s",
428 options.instance, strerror(rc));
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, 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);
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 pcmk__xml_free(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 pcmk__xml_free(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 const char *reason = "The reset command overwrites the active "
777 "shadow configuration";
778
779 exit_code = CRM_EX_USAGE;
780 set_danger_error(reason, true, true, error);
781 goto done;
782 }
783 if (check_file_exists(filename, reset, error) != pcmk_rc_ok) {
784 goto done;
785 }
786 }
787
788 if (query_real_cib(&output, error) != pcmk_rc_ok) {
789 goto done;
790 }
791
792 if (write_shadow_file(output, filename, reset, error) != pcmk_rc_ok) {
793 goto done;
794 }
795 shadow_setup(out, false, error);
796
797 done:
798 free(filename);
799 pcmk__xml_free(output);
800 }
801
802
803
804
805
806
807
808
809 static void
810 delete_shadow_file(pcmk__output_t *out, GError **error)
811 {
812 char *filename = NULL;
813
814 if (!options.force) {
815 const char *reason = "The delete command removes the specified shadow "
816 "file";
817
818 exit_code = CRM_EX_USAGE;
819 set_danger_error(reason, true, true, error);
820 return;
821 }
822
823 filename = get_shadow_file(options.instance);
824
825 if ((unlink(filename) < 0) && (errno != ENOENT)) {
826 exit_code = pcmk_rc2exitc(errno);
827 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
828 "Could not remove shadow instance '%s': %s",
829 options.instance, strerror(errno));
830 } else {
831 shadow_teardown(out);
832 }
833 free(filename);
834 }
835
836
837
838
839
840
841
842
843
844 static void
845 edit_shadow_file(GError **error)
846 {
847 char *filename = NULL;
848 const char *editor = NULL;
849
850 if (get_instance_from_env(error) != pcmk_rc_ok) {
851 return;
852 }
853
854 filename = get_shadow_file(options.instance);
855 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
856 goto done;
857 }
858
859 editor = getenv("EDITOR");
860 if (editor == NULL) {
861 exit_code = CRM_EX_NOT_CONFIGURED;
862 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
863 "No value for EDITOR defined");
864 goto done;
865 }
866
867 execlp(editor, "--", filename, NULL);
868 exit_code = CRM_EX_OSFILE;
869 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
870 "Could not invoke EDITOR (%s %s): %s",
871 editor, filename, strerror(errno));
872
873 done:
874 free(filename);
875 }
876
877
878
879
880
881
882
883
884 static void
885 show_shadow_contents(pcmk__output_t *out, GError **error)
886 {
887 char *filename = NULL;
888
889 if (get_instance_from_env(error) != pcmk_rc_ok) {
890 return;
891 }
892
893 filename = get_shadow_file(options.instance);
894
895 if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
896 xmlNode *output = NULL;
897 bool quiet_orig = out->quiet;
898
899 if (read_xml(filename, &output, error) != pcmk_rc_ok) {
900 goto done;
901 }
902
903 out->quiet = true;
904 out->message(out, "shadow",
905 options.instance, NULL, output, NULL, shadow_disp_content);
906 out->quiet = quiet_orig;
907
908 pcmk__xml_free(output);
909 }
910
911 done:
912 free(filename);
913 }
914
915
916
917
918
919
920
921
922 static void
923 show_shadow_diff(pcmk__output_t *out, GError **error)
924 {
925 char *filename = NULL;
926 xmlNodePtr old_config = NULL;
927 xmlNodePtr new_config = NULL;
928 xmlNodePtr diff = NULL;
929 bool quiet_orig = out->quiet;
930
931 if (get_instance_from_env(error) != pcmk_rc_ok) {
932 return;
933 }
934
935 filename = get_shadow_file(options.instance);
936 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
937 goto done;
938 }
939
940 if (query_real_cib(&old_config, error) != pcmk_rc_ok) {
941 goto done;
942 }
943
944 if (read_xml(filename, &new_config, error) != pcmk_rc_ok) {
945 goto done;
946 }
947 pcmk__xml_mark_changes(old_config, new_config);
948 diff = xml_create_patchset(0, old_config, new_config, NULL, false);
949
950 pcmk__log_xml_changes(LOG_INFO, new_config);
951 pcmk__xml_commit_changes(new_config->doc);
952
953 out->quiet = true;
954 out->message(out, "shadow",
955 options.instance, NULL, NULL, diff, shadow_disp_diff);
956 out->quiet = quiet_orig;
957
958 if (diff != NULL) {
959
960
961
962
963 exit_code = CRM_EX_ERROR;
964 }
965
966 done:
967 free(filename);
968 pcmk__xml_free(old_config);
969 pcmk__xml_free(new_config);
970 pcmk__xml_free(diff);
971 }
972
973
974
975
976
977
978
979
980 static void
981 show_shadow_filename(pcmk__output_t *out, GError **error)
982 {
983 if (get_instance_from_env(error) == pcmk_rc_ok) {
984 char *filename = get_shadow_file(options.instance);
985 bool quiet_orig = out->quiet;
986
987 out->quiet = true;
988 out->message(out, "shadow",
989 options.instance, filename, NULL, NULL, shadow_disp_file);
990 out->quiet = quiet_orig;
991
992 free(filename);
993 }
994 }
995
996
997
998
999
1000
1001
1002
1003 static void
1004 show_shadow_instance(pcmk__output_t *out, GError **error)
1005 {
1006 if (get_instance_from_env(error) == pcmk_rc_ok) {
1007 bool quiet_orig = out->quiet;
1008
1009 out->quiet = true;
1010 out->message(out, "shadow",
1011 options.instance, NULL, NULL, NULL, shadow_disp_instance);
1012 out->quiet = quiet_orig;
1013 }
1014 }
1015
1016
1017
1018
1019
1020
1021
1022
1023 static void
1024 switch_shadow_instance(pcmk__output_t *out, GError **error)
1025 {
1026 char *filename = NULL;
1027
1028 filename = get_shadow_file(options.instance);
1029 if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
1030 shadow_setup(out, true, error);
1031 }
1032 free(filename);
1033 }
1034
1035 static gboolean
1036 command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
1037 GError **error)
1038 {
1039 if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) {
1040 options.cmd = shadow_cmd_which;
1041
1042 } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) {
1043 options.cmd = shadow_cmd_display;
1044
1045 } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) {
1046 options.cmd = shadow_cmd_diff;
1047
1048 } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) {
1049 options.cmd = shadow_cmd_file;
1050
1051 } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) {
1052 options.cmd = shadow_cmd_create;
1053
1054 } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) {
1055 options.cmd = shadow_cmd_create_empty;
1056
1057 } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) {
1058 options.cmd = shadow_cmd_commit;
1059
1060 } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
1061 options.cmd = shadow_cmd_delete;
1062
1063 } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) {
1064 options.cmd = shadow_cmd_edit;
1065
1066 } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) {
1067 options.cmd = shadow_cmd_reset;
1068
1069 } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) {
1070 options.cmd = shadow_cmd_switch;
1071
1072 } else {
1073
1074 return FALSE;
1075 }
1076
1077
1078 pcmk__str_update(&options.instance, optarg);
1079 return TRUE;
1080 }
1081
1082 static GOptionEntry query_entries[] = {
1083 { "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1084 "Indicate the active shadow copy", NULL },
1085
1086 { "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1087 "Display the contents of the active shadow copy", NULL },
1088
1089 { "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1090 "Display the changes in the active shadow copy", NULL },
1091
1092 { "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1093 "Display the location of the active shadow copy file", NULL },
1094
1095 { NULL }
1096 };
1097
1098 static GOptionEntry command_entries[] = {
1099 { "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1100 "Create the named shadow copy of the active cluster configuration",
1101 "name" },
1102
1103 { "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
1104 command_cb,
1105 "Create the named shadow copy with an empty cluster configuration.\n"
1106 INDENT "Optional: --validate-with", "name" },
1107
1108 { "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1109 "Upload the contents of the named shadow copy to the cluster", "name" },
1110
1111 { "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1112 "Delete the contents of the named shadow copy", "name" },
1113
1114 { "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
1115 "Edit the contents of the active shadow copy with your favorite $EDITOR",
1116 NULL },
1117
1118 { "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1119 "Recreate named shadow copy from the active cluster configuration",
1120 "name. Required: --force." },
1121
1122 { "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
1123 "(Advanced) Switch to the named shadow copy", "name" },
1124
1125 { NULL }
1126 };
1127
1128 static GOptionEntry addl_entries[] = {
1129 { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
1130 "(Advanced) Force the action to be performed", NULL },
1131
1132 { "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch,
1133 "(Advanced) Don't spawn a new shell", NULL },
1134
1135 { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload,
1136 "(Advanced) Upload entire CIB, including status, with --commit", NULL },
1137
1138 { "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
1139 &options.validate_with,
1140 "(Advanced) Create an older configuration version", NULL },
1141
1142 { NULL }
1143 };
1144
1145 static GOptionContext *
1146 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
1147 {
1148 const char *desc = NULL;
1149 GOptionContext *context = NULL;
1150
1151 desc = "Examples:\n\n"
1152 "Create a blank shadow configuration:\n\n"
1153 "\t# crm_shadow --create-empty myShadow\n\n"
1154 "Create a shadow configuration from the running cluster\n\n"
1155 "\t# crm_shadow --create myShadow\n\n"
1156 "Display the current shadow configuration:\n\n"
1157 "\t# crm_shadow --display\n\n"
1158 "Discard the current shadow configuration (named myShadow):\n\n"
1159 "\t# crm_shadow --delete myShadow --force\n\n"
1160 "Upload current shadow configuration (named myShadow) to running "
1161 "cluster:\n\n"
1162 "\t# crm_shadow --commit myShadow\n\n";
1163
1164 context = pcmk__build_arg_context(args, "text (default), xml", group,
1165 "<query>|<command>");
1166 g_option_context_set_description(context, desc);
1167
1168 pcmk__add_arg_group(context, "queries", "Queries:",
1169 "Show query help", query_entries);
1170 pcmk__add_arg_group(context, "commands", "Commands:",
1171 "Show command help", command_entries);
1172 pcmk__add_arg_group(context, "additional", "Additional Options:",
1173 "Show additional options", addl_entries);
1174 return context;
1175 }
1176
1177 int
1178 main(int argc, char **argv)
1179 {
1180 int rc = pcmk_rc_ok;
1181 pcmk__output_t *out = NULL;
1182
1183 GError *error = NULL;
1184
1185 GOptionGroup *output_group = NULL;
1186 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1187 gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv");
1188 GOptionContext *context = build_arg_context(args, &output_group);
1189
1190 crm_log_preinit(NULL, argc, argv);
1191
1192 pcmk__register_formats(output_group, formats);
1193
1194 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1195 exit_code = CRM_EX_USAGE;
1196 goto done;
1197 }
1198
1199 rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1200 if (rc != pcmk_rc_ok) {
1201 exit_code = CRM_EX_ERROR;
1202 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1203 "Error creating output format %s: %s", args->output_ty,
1204 pcmk_rc_str(rc));
1205 goto done;
1206 }
1207
1208 if (g_strv_length(processed_args) > 1) {
1209 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1210 GString *extra = g_string_sized_new(128);
1211
1212 for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
1213 if (extra->len > 0) {
1214 g_string_append_c(extra, ' ');
1215 }
1216 g_string_append(extra, processed_args[lpc]);
1217 }
1218
1219 exit_code = CRM_EX_USAGE;
1220 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1221 "non-option ARGV-elements: %s\n\n%s", extra->str, help);
1222 g_free(help);
1223 g_string_free(extra, TRUE);
1224 goto done;
1225 }
1226
1227 if (args->version) {
1228 out->version(out, false);
1229 goto done;
1230 }
1231
1232 pcmk__register_messages(out, fmt_functions);
1233
1234 if (options.cmd == shadow_cmd_none) {
1235
1236 gchar *help = g_option_context_get_help(context, TRUE, NULL);
1237
1238 exit_code = CRM_EX_USAGE;
1239 g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1240 "Must specify a query or command option\n\n%s", help);
1241 g_free(help);
1242 goto done;
1243 }
1244
1245 pcmk__cli_init_logging("crm_shadow", args->verbosity);
1246
1247 if (args->verbosity > 0) {
1248 cib__set_call_options(options.cmd_options, crm_system_name,
1249 cib_verbose);
1250 }
1251
1252
1253 switch (options.cmd) {
1254 case shadow_cmd_commit:
1255 commit_shadow_file(&error);
1256 break;
1257 case shadow_cmd_create:
1258 create_shadow_from_cib(out, false, &error);
1259 break;
1260 case shadow_cmd_create_empty:
1261 create_shadow_empty(out, &error);
1262 break;
1263 case shadow_cmd_reset:
1264 create_shadow_from_cib(out, true, &error);
1265 break;
1266 case shadow_cmd_delete:
1267 delete_shadow_file(out, &error);
1268 break;
1269 case shadow_cmd_diff:
1270 show_shadow_diff(out, &error);
1271 break;
1272 case shadow_cmd_display:
1273 show_shadow_contents(out, &error);
1274 break;
1275 case shadow_cmd_edit:
1276 edit_shadow_file(&error);
1277 break;
1278 case shadow_cmd_file:
1279 show_shadow_filename(out, &error);
1280 break;
1281 case shadow_cmd_switch:
1282 switch_shadow_instance(out, &error);
1283 break;
1284 case shadow_cmd_which:
1285 show_shadow_instance(out, &error);
1286 break;
1287 default:
1288
1289 break;
1290 }
1291
1292 done:
1293 g_strfreev(processed_args);
1294 pcmk__free_arg_context(context);
1295
1296 pcmk__output_and_clear_error(&error, out);
1297
1298 free(options.instance);
1299 g_free(options.validate_with);
1300
1301 if (out != NULL) {
1302 out->finish(out, exit_code, true, NULL);
1303 pcmk__output_free(out);
1304 }
1305
1306 crm_exit(exit_code);
1307 }