This source file includes following definitions.
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- PCMK__OUTPUT_ARGS
- set_danger_error
- get_instance_from_env
- check_file_exists
- connect_real_cib
- query_real_cib
- read_xml
- write_shadow_file
- get_shadow_prompt
- shadow_setup
- shadow_teardown
- commit_shadow_file
- create_shadow_empty
- create_shadow_from_cib
- delete_shadow_file
- edit_shadow_file
- show_shadow_contents
- show_shadow_diff
- show_shadow_filename
- show_shadow_instance
- switch_shadow_instance
- command_cb
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <unistd.h>
14
15 #include <sys/param.h>
16 #include <crm/crm.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <fcntl.h>
23
24 #include <crm/common/cmdline_internal.h>
25 #include <crm/common/ipc.h>
26 #include <crm/common/output_internal.h>
27 #include <crm/common/xml.h>
28
29 #include <crm/cib.h>
30 #include <crm/cib/internal.h>
31
32 #define SUMMARY "perform Pacemaker configuration changes in a sandbox\n\n" \
33 "This command sets up an environment in which " \
34 "configuration tools (cibadmin,\n" \
35 "crm_resource, etc.) work offline instead of against a " \
36 "live cluster, allowing\n" \
37 "changes to be previewed and tested for side effects."
38
39 #define INDENT " "
40
41 enum shadow_command {
42 shadow_cmd_none = 0,
43 shadow_cmd_which,
44 shadow_cmd_display,
45 shadow_cmd_diff,
46 shadow_cmd_file,
47 shadow_cmd_create,
48 shadow_cmd_create_empty,
49 shadow_cmd_commit,
50 shadow_cmd_delete,
51 shadow_cmd_edit,
52 shadow_cmd_reset,
53 shadow_cmd_switch,
54 };
55
56
57
58
59
60
61
62
63 enum shadow_disp_flags {
64 shadow_disp_instance = (1 << 0),
65 shadow_disp_file = (1 << 1),
66 shadow_disp_content = (1 << 2),
67 shadow_disp_diff = (1 << 3),
68 };
69
70 static crm_exit_t exit_code = CRM_EX_OK;
71
72 static struct {
73 enum shadow_command cmd;
74 int cmd_options;
75 char *instance;
76 gboolean force;
77 gboolean batch;
78 gboolean full_upload;
79 gchar *validate_with;
80 } options = {
81 .cmd_options = cib_sync_call,
82 };
83
84
85
86
87
88
89
90
91
92
93
94
95
96 PCMK__OUTPUT_ARGS("instruction", "const char *")
97 static int
98 instruction_default(pcmk__output_t *out, va_list args)
99 {
100 const char *msg = va_arg(args, const char *);
101
102 if (msg == NULL) {
103 return pcmk_rc_no_output;
104 }
105 return out->info(out, "%s", msg);
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120 PCMK__OUTPUT_ARGS("instruction", "const char *")
121 static int
122 instruction_xml(pcmk__output_t *out, va_list args)
123 {
124 const char *msg = va_arg(args, const char *);
125
126 if (msg == NULL) {
127 return pcmk_rc_no_output;
128 }
129 pcmk__output_create_xml_text_node(out, "instruction", msg);
130 return pcmk_rc_ok;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
150 "const xmlNode *", "enum shadow_disp_flags")
151 static int
152 shadow_default(pcmk__output_t *out, va_list args)
153 {
154 const char *instance = va_arg(args, const char *);
155 const char *filename = va_arg(args, const char *);
156 const xmlNode *content = va_arg(args, const xmlNode *);
157 const xmlNode *diff = va_arg(args, const xmlNode *);
158 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
159
160 int rc = pcmk_rc_no_output;
161
162 if (pcmk_is_set(flags, shadow_disp_instance)) {
163 rc = out->info(out, "Instance: %s", pcmk__s(instance, "<unknown>"));
164 }
165 if (pcmk_is_set(flags, shadow_disp_file)) {
166 rc = out->info(out, "File name: %s", pcmk__s(filename, "<unknown>"));
167 }
168 if (pcmk_is_set(flags, shadow_disp_content)) {
169 rc = out->info(out, "Content:");
170
171 if (content != NULL) {
172 GString *buf = g_string_sized_new(1024);
173 gchar *str = NULL;
174
175 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
176 buf, 0);
177
178 str = g_string_free(buf, FALSE);
179 str = pcmk__trim(str);
180 if (!pcmk__str_empty(str)) {
181 out->info(out, "%s", str);
182 }
183 g_free(str);
184
185 } else {
186 out->info(out, "<unknown>");
187 }
188 }
189 if (pcmk_is_set(flags, shadow_disp_diff)) {
190 rc = out->info(out, "Diff:");
191
192 if (diff != NULL) {
193 out->message(out, "xml-patchset", diff);
194 } else {
195 out->info(out, "<empty>");
196 }
197 }
198
199 return rc;
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
219 "const xmlNode *", "enum shadow_disp_flags")
220 static int
221 shadow_text(pcmk__output_t *out, va_list args)
222 {
223 if (!out->is_quiet(out)) {
224 return shadow_default(out, args);
225
226 } else {
227 const char *instance = va_arg(args, const char *);
228 const char *filename = va_arg(args, const char *);
229 const xmlNode *content = va_arg(args, const xmlNode *);
230 const xmlNode *diff = va_arg(args, const xmlNode *);
231 enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
232
233 int rc = pcmk_rc_no_output;
234 bool quiet_orig = out->quiet;
235
236
237
238
239 out->quiet = false;
240
241 if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) {
242 rc = out->info(out, "%s", instance);
243 }
244 if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) {
245 rc = out->info(out, "%s", filename);
246 }
247 if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) {
248 GString *buf = g_string_sized_new(1024);
249 gchar *str = NULL;
250
251 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
252 buf, 0);
253
254 str = g_string_free(buf, FALSE);
255 str = pcmk__trim(str);
256 rc = out->info(out, "%s", str);
257 g_free(str);
258 }
259 if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) {
260 rc = out->message(out, "xml-patchset", diff);
261 }
262
263 out->quiet = quiet_orig;
264 return rc;
265 }
266 }
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
286 "const xmlNode *", "enum shadow_disp_flags")
287 static int
288 shadow_xml(pcmk__output_t *out, va_list args)
289 {
290 const char *instance = va_arg(args, const char *);
291 const char *filename = va_arg(args, const char *);
292 const xmlNode *content = va_arg(args, const xmlNode *);
293 const xmlNode *diff = va_arg(args, const xmlNode *);
294 enum shadow_disp_flags flags G_GNUC_UNUSED =
295 (enum shadow_disp_flags) va_arg(args, int);
296
297 pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW,
298 PCMK_XA_INSTANCE, instance,
299 PCMK_XA_FILE, filename,
300 NULL);
301
302 if (content != NULL) {
303 GString *buf = g_string_sized_new(1024);
304
305 pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
306 0);
307
308 out->output_xml(out, PCMK_XE_CONTENT, buf->str);
309 g_string_free(buf, TRUE);
310 }
311
312 if (diff != NULL) {
313 out->message(out, "xml-patchset", diff);
314 }
315
316 pcmk__output_xml_pop_parent(out);
317 return pcmk_rc_ok;
318 }
319
320 static const pcmk__supported_format_t formats[] = {
321 PCMK__SUPPORTED_FORMAT_NONE,
322 PCMK__SUPPORTED_FORMAT_TEXT,
323 PCMK__SUPPORTED_FORMAT_XML,
324 { NULL, NULL, NULL }
325 };
326
327 static const pcmk__message_entry_t fmt_functions[] = {
328 { "instruction", "default", instruction_default },
329 { "instruction", "xml", instruction_xml },
330 { "shadow", "default", shadow_default },
331 { "shadow", "text", shadow_text },
332 { "shadow", "xml", shadow_xml },
333
334 { NULL, NULL, NULL }
335 };
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 static void
351 set_danger_error(const char *reason, bool for_shadow, bool show_mismatch,
352 GError **error)
353 {
354 const char *active = getenv("CIB_shadow");
355 char *full = NULL;
356
357 if (show_mismatch
358 && !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) {
359
360 full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow "
361 "instance (%s) is not the same as the active "
362 "one (%s)",
363 reason, options.instance, active);
364 reason = full;
365 }
366
367 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
368 "%s%sTo prevent accidental destruction of the %s, the --force "
369 "flag is required in order to proceed.",
370 pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""),
371 (for_shadow? "shadow file" : "cluster"));
372 free(full);
373 }
374
375
376
377
378
379
380
381
382
383 static int
384 get_instance_from_env(GError **error)
385 {
386 int rc = pcmk_rc_ok;
387
388 pcmk__str_update(&options.instance, getenv("CIB_shadow"));
389 if (options.instance == NULL) {
390 rc = ENXIO;
391 exit_code = pcmk_rc2exitc(rc);
392 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
393 "No active shadow configuration defined");
394 }
395 return rc;
396 }
397
398
399
400
401
402
403
404
405
406
407
408 static int
409 check_file_exists(const char *filename, bool should_exist, GError **error)
410 {
411 struct stat buf;
412
413 if (!should_exist && (stat(filename, &buf) == 0)) {
414 char *reason = crm_strdup_printf("A shadow instance '%s' already "
415 "exists", options.instance);
416
417 exit_code = CRM_EX_CANTCREAT;
418 set_danger_error(reason, true, false, error);
419 free(reason);
420 return EEXIST;
421 }
422
423 if (should_exist && (stat(filename, &buf) < 0)) {
424 int rc = errno;
425
426 exit_code = pcmk_rc2exitc(rc);
427 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
428 "Could not access shadow instance '%s': %s",
429 options.instance, strerror(rc));
430 return errno;
431 }
432
433 return pcmk_rc_ok;
434 }
435
436
437
438
439
440
441
442
443
444
445 static int
446 connect_real_cib(cib_t **real_cib, GError **error)
447 {
448 int rc = pcmk_rc_ok;
449
450 *real_cib = cib_new_no_shadow();
451 if (*real_cib == NULL) {
452 rc = ENOMEM;
453 exit_code = pcmk_rc2exitc(rc);
454 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
455 "Could not create a CIB connection object");
456 return rc;
457 }
458
459 rc = cib__signon_attempts(*real_cib, cib_command, 5);
460 rc = pcmk_legacy2rc(rc);
461 if (rc != pcmk_rc_ok) {
462 exit_code = pcmk_rc2exitc(rc);
463 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
464 "Could not connect to CIB: %s", pcmk_rc_str(rc));
465 }
466 return rc;
467 }
468
469
470
471
472
473
474
475
476
477
478 static int
479 query_real_cib(xmlNode **output, GError **error)
480 {
481 cib_t *real_cib = NULL;
482 int rc = connect_real_cib(&real_cib, error);
483
484 if (rc != pcmk_rc_ok) {
485 goto done;
486 }
487
488 rc = real_cib->cmds->query(real_cib, NULL, output, options.cmd_options);
489 rc = pcmk_legacy2rc(rc);
490 if (rc != pcmk_rc_ok) {
491 exit_code = pcmk_rc2exitc(rc);
492 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
493 "Could not query the non-shadow CIB: %s", pcmk_rc_str(rc));
494 }
495
496 done:
497 cib_delete(real_cib);
498 return rc;
499 }
500
501
502
503
504
505
506
507
508
509
510
511 static int
512 read_xml(const char *filename, xmlNode **output, GError **error)
513 {
514 int rc = pcmk_rc_ok;
515
516 *output = pcmk__xml_read(filename);
517 if (*output == NULL) {
518 rc = pcmk_rc_no_input;
519 exit_code = pcmk_rc2exitc(rc);
520 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
521 "Could not parse XML from input file '%s'", filename);
522 }
523 return rc;
524 }
525
526
527
528
529
530
531
532
533
534
535 static int
536 write_shadow_file(const xmlNode *xml, const char *filename, bool reset,
537 GError **error)
538 {
539 int rc = pcmk__xml_write_file(xml, filename, false);
540
541 if (rc != pcmk_rc_ok) {
542 exit_code = pcmk_rc2exitc(rc);
543 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
544 "Could not %s the shadow instance '%s': %s",
545 reset? "reset" : "create", options.instance,
546 pcmk_rc_str(rc));
547 }
548 return rc;
549 }
550
551
552
553
554
555
556
557
558
559 static inline char *
560 get_shadow_prompt(void)
561 {
562 return crm_strdup_printf("shadow[%.40s] # ", options.instance);
563 }
564
565
566
567
568
569
570
571
572
573
574 static void
575 shadow_setup(pcmk__output_t *out, bool do_switch, GError **error)
576 {
577 const char *active = getenv("CIB_shadow");
578 const char *prompt = getenv("PS1");
579 const char *shell = getenv("SHELL");
580 char *new_prompt = get_shadow_prompt();
581
582 if (pcmk__str_eq(active, options.instance, pcmk__str_none)
583 && pcmk__str_eq(new_prompt, prompt, pcmk__str_none)) {
584
585 goto done;
586 }
587
588 if (!options.batch && (shell != NULL)) {
589 out->info(out, "Setting up shadow instance");
590 setenv("PS1", new_prompt, 1);
591 setenv("CIB_shadow", options.instance, 1);
592
593 out->message(out, PCMK_XE_INSTRUCTION,
594 "Press Ctrl+D to exit the crm_shadow shell");
595
596 if (pcmk__str_eq(shell, "(^|/)bash$", pcmk__str_regex)) {
597 execl(shell, shell, "--norc", "--noprofile", NULL);
598 } else {
599 execl(shell, shell, NULL);
600 }
601
602 exit_code = pcmk_rc2exitc(errno);
603 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
604 "Failed to launch shell '%s': %s",
605 shell, pcmk_rc_str(errno));
606
607 } else {
608 char *msg = NULL;
609 const char *prefix = "A new shadow instance was created. To begin "
610 "using it";
611
612 if (do_switch) {
613 prefix = "To switch to the named shadow instance";
614 }
615
616 msg = crm_strdup_printf("%s, enter the following into your shell:\n"
617 "\texport CIB_shadow=%s",
618 prefix, options.instance);
619 out->message(out, "instruction", msg);
620 free(msg);
621 }
622
623 done:
624 free(new_prompt);
625 }
626
627
628
629
630
631
632
633 static void
634 shadow_teardown(pcmk__output_t *out)
635 {
636 const char *active = getenv("CIB_shadow");
637 const char *prompt = getenv("PS1");
638
639 if (pcmk__str_eq(active, options.instance, pcmk__str_none)) {
640 char *our_prompt = get_shadow_prompt();
641
642 if (pcmk__str_eq(prompt, our_prompt, pcmk__str_none)) {
643 out->message(out, "instruction",
644 "Press Ctrl+D to exit the crm_shadow shell");
645
646 } else {
647 out->message(out, "instruction",
648 "Remember to unset the CIB_shadow variable by "
649 "entering the following into your shell:\n"
650 "\tunset CIB_shadow");
651 }
652 free(our_prompt);
653 }
654 }
655
656
657
658
659
660
661
662 static void
663 commit_shadow_file(GError **error)
664 {
665 char *filename = NULL;
666 cib_t *real_cib = NULL;
667
668 xmlNodePtr input = NULL;
669 xmlNodePtr section_xml = NULL;
670 const char *section = NULL;
671
672 int rc = pcmk_rc_ok;
673
674 if (!options.force) {
675 const char *reason = "The commit command overwrites the active cluster "
676 "configuration";
677
678 exit_code = CRM_EX_USAGE;
679 set_danger_error(reason, false, true, error);
680 return;
681 }
682
683 filename = get_shadow_file(options.instance);
684 if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
685 goto done;
686 }
687
688 if (connect_real_cib(&real_cib, error) != pcmk_rc_ok) {
689 goto done;
690 }
691
692 if (read_xml(filename, &input, error) != pcmk_rc_ok) {
693 goto done;
694 }
695
696 section_xml = input;
697
698 if (!options.full_upload) {
699 section = PCMK_XE_CONFIGURATION;
700 section_xml = pcmk__xe_first_child(input, section, NULL, NULL);
701 }
702
703 rc = real_cib->cmds->replace(real_cib, section, section_xml,
704 options.cmd_options);
705 rc = pcmk_legacy2rc(rc);
706
707 if (rc != pcmk_rc_ok) {
708 exit_code = pcmk_rc2exitc(rc);
709 g_set_error(error, PCMK__EXITC_ERROR, exit_code,
710 "Could not commit shadow instance '%s' to the CIB: %s",
711 options.instance, pcmk_rc_str(rc));
712 }
713
714 done:
715 free(filename);
716 cib_delete(real_cib);
717 pcmk__xml_free(input);
718 }
719
720
721
722
723
724
725
726
727
728
729
730 static void
731 create_shadow_empty(pcmk__output_t *out, GError **error)
732 {
733 char *filename = get_shadow_file(options.instance);
734 xmlNode *output = NULL;
735
736 if (!options.force
737 && (check_file_exists(filename, false, error) != pcmk_rc_ok)) {
738 goto done;
739 }
740
741 output = createEmptyCib(0);
742 crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
743 out->info(out, "Created new %s configuration",
744 crm_element_value(output, PCMK_XA_VALIDATE_WITH));
745
746 if (write_shadow_file(output, filename, false, error) != pcmk_rc_ok) {
747 goto done;
748 }
749 shadow_setup(out, false, error);
750
751 done:
752 free(filename);
753 pcmk__xml_free(output);
754 }
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769 static void
770 create_shadow_from_cib(pcmk__output_t *out, bool reset, GError **error)
771 {
772 char *filename = get_shadow_file(options.instance);
773 xmlNode *output = NULL;
774
775 if (!options.force) {
776 if (reset) {
777 const char *reason = "The reset command overwrites the active "
778 "shadow configuration";
779
780 exit_code = CRM_EX_USAGE;
781 set_danger_error(reason, true, true, error);
782 goto done;
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 pcmk__xml_free(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 pcmk__xml_free(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 pcmk__xml_free(old_config);
971 pcmk__xml_free(new_config);
972 pcmk__xml_free(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. Required: --force." },
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 }