This source file includes following definitions.
- get_shadow_prompt
- shadow_setup
- shadow_teardown
- main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 #include <crm_internal.h>
21
22 #include <stdio.h>
23 #include <unistd.h>
24
25 #include <sys/param.h>
26 #include <crm/crm.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <crm/msg_xml.h>
34 #include <crm/common/xml.h>
35
36 #include <crm/common/ipc.h>
37
38 #include <crm/cib.h>
39
40 int exit_code = pcmk_ok;
41 GMainLoop *mainloop = NULL;
42
43 const char *host = NULL;
44 void usage(const char *cmd, int exit_status);
45
46 int command_options = cib_sync_call;
47 const char *cib_action = NULL;
48
49 cib_t *real_cib = NULL;
50
51 static int force_flag = 0;
52 static int batch_flag = 0;
53
54 static char *
55 get_shadow_prompt(const char *name)
56 {
57 return crm_strdup_printf("shadow[%.40s] # ", name);
58 }
59
60 static void
61 shadow_setup(char *name, gboolean do_switch)
62 {
63 const char *prompt = getenv("PS1");
64 const char *shell = getenv("SHELL");
65 char *new_prompt = get_shadow_prompt(name);
66
67 printf("Setting up shadow instance\n");
68
69 if (safe_str_eq(new_prompt, prompt)) {
70
71 goto done;
72
73 } else if (batch_flag == FALSE && shell != NULL) {
74 setenv("PS1", new_prompt, 1);
75 setenv("CIB_shadow", name, 1);
76 printf("Type Ctrl-D to exit the crm_shadow shell\n");
77
78 if (strstr(shell, "bash")) {
79 execl(shell, shell, "--norc", "--noprofile", NULL);
80 } else {
81 execl(shell, shell, NULL);
82 }
83
84 } else if (do_switch) {
85 printf("To switch to the named shadow instance, paste the following into your shell:\n");
86
87 } else {
88 printf
89 ("A new shadow instance was created. To begin using it paste the following into your shell:\n");
90 }
91 printf(" CIB_shadow=%s ; export CIB_shadow\n", name);
92
93 done:
94 free(new_prompt);
95 }
96
97 static void
98 shadow_teardown(char *name)
99 {
100 const char *prompt = getenv("PS1");
101 char *our_prompt = get_shadow_prompt(name);
102
103 if (prompt != NULL && strstr(prompt, our_prompt)) {
104 printf("Now type Ctrl-D to exit the crm_shadow shell\n");
105
106 } else {
107 printf
108 ("Please remember to unset the CIB_shadow variable by pasting the following into your shell:\n");
109 printf(" unset CIB_shadow\n");
110 }
111 free(our_prompt);
112 }
113
114
115 static struct crm_option long_options[] = {
116
117 {"help", 0, 0, '?', "\t\tThis text"},
118 {"version", 0, 0, '$', "\t\tVersion information" },
119 {"verbose", 0, 0, 'V', "\t\tIncrease debug output"},
120
121 {"-spacer-", 1, 0, '-', "\nQueries:"},
122 {"which", no_argument, NULL, 'w', "\t\tIndicate the active shadow copy"},
123 {"display", no_argument, NULL, 'p', "\t\tDisplay the contents of the active shadow copy"},
124 {"edit", no_argument, NULL, 'E', "\t\tEdit the contents of the active shadow copy with your favorite $EDITOR"},
125 {"diff", no_argument, NULL, 'd', "\t\tDisplay the changes in the active shadow copy\n"},
126 {"file", no_argument, NULL, 'F', "\t\tDisplay the location of the active shadow copy file\n"},
127
128 {"-spacer-", 1, 0, '-', "\nCommands:"},
129 {"create", required_argument, NULL, 'c', "\tCreate the named shadow copy of the active cluster configuration"},
130 {"create-empty", required_argument, NULL, 'e', "Create the named shadow copy with an empty cluster configuration. Optional: --validate-with"},
131 {"commit", required_argument, NULL, 'C', "\tUpload the contents of the named shadow copy to the cluster"},
132 {"delete", required_argument, NULL, 'D', "\tDelete the contents of the named shadow copy"},
133 {"reset", required_argument, NULL, 'r', "\tRecreate the named shadow copy from the active cluster configuration"},
134 {"switch", required_argument, NULL, 's', "\t(Advanced) Switch to the named shadow copy"},
135
136 {"-spacer-", 1, 0, '-', "\nAdditional Options:"},
137 {"force", no_argument, NULL, 'f', "\t\t(Advanced) Force the action to be performed"},
138 {"batch", no_argument, NULL, 'b', "\t\t(Advanced) Don't spawn a new shell" },
139 {"all", no_argument, NULL, 'a', "\t\t(Advanced) Upload the entire CIB, including status, with --commit" },
140 {"validate-with", required_argument, NULL, 'v', "(Advanced) Create an older configuration version" },
141
142 {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph},
143 {"-spacer-", 1, 0, '-', "Create a blank shadow configuration:", pcmk_option_paragraph},
144 {"-spacer-", 1, 0, '-', " crm_shadow --create-empty myShadow", pcmk_option_example},
145 {"-spacer-", 1, 0, '-', "Create a shadow configuration from the running cluster:", pcmk_option_paragraph},
146 {"-spacer-", 1, 0, '-', " crm_shadow --create myShadow", pcmk_option_example},
147 {"-spacer-", 1, 0, '-', "Display the current shadow configuration:", pcmk_option_paragraph},
148 {"-spacer-", 1, 0, '-', " crm_shadow --display", pcmk_option_example},
149 {"-spacer-", 1, 0, '-', "Discard the current shadow configuration (named myShadow):", pcmk_option_paragraph},
150 {"-spacer-", 1, 0, '-', " crm_shadow --delete myShadow", pcmk_option_example},
151 {"-spacer-", 1, 0, '-', "Upload the current shadow configuration (named myShadow) to the running cluster:", pcmk_option_paragraph},
152 {"-spacer-", 1, 0, '-', " crm_shadow --commit myShadow", pcmk_option_example},
153
154 {0, 0, 0, 0}
155 };
156
157
158 int
159 main(int argc, char **argv)
160 {
161 int rc = 0;
162 int flag;
163 int argerr = 0;
164 static int command = '?';
165 const char *validation = NULL;
166 char *shadow = NULL;
167 char *shadow_file = NULL;
168 gboolean full_upload = FALSE;
169 gboolean dangerous_cmd = FALSE;
170 struct stat buf;
171 int option_index = 0;
172
173 crm_log_cli_init("crm_shadow");
174 crm_set_options(NULL, "(query|command) [modifiers]", long_options,
175 "Perform configuration changes in a sandbox before updating the live cluster."
176 "\n\nSets up an environment in which configuration tools (cibadmin, crm_resource, etc) work"
177 " offline instead of against a live cluster, allowing changes to be previewed and tested"
178 " for side-effects.\n");
179
180 if (argc < 2) {
181 crm_help('?', EX_USAGE);
182 }
183
184 while (1) {
185 flag = crm_get_option(argc, argv, &option_index);
186 if (flag == -1 || flag == 0)
187 break;
188
189 switch (flag) {
190 case 'a':
191 full_upload = TRUE;
192 break;
193 case 'd':
194 case 'E':
195 case 'p':
196 case 'w':
197 case 'F':
198 command = flag;
199 free(shadow);
200 shadow = NULL;
201 {
202 const char *env = getenv("CIB_shadow");
203 if(env) {
204 shadow = strdup(env);
205 } else {
206 fprintf(stderr, "No active shadow configuration defined\n");
207 crm_exit(ENOENT);
208 }
209 }
210 break;
211 case 'v':
212 validation = optarg;
213 break;
214 case 'e':
215 case 'c':
216 case 's':
217 case 'r':
218 command = flag;
219 free(shadow);
220 shadow = strdup(optarg);
221 break;
222 case 'C':
223 case 'D':
224 command = flag;
225 dangerous_cmd = TRUE;
226 free(shadow);
227 shadow = strdup(optarg);
228 break;
229 case 'V':
230 command_options = command_options | cib_verbose;
231 crm_bump_log_level(argc, argv);
232 break;
233 case '$':
234 case '?':
235 crm_help(flag, EX_OK);
236 break;
237 case 'f':
238 command_options |= cib_quorum_override;
239 force_flag = 1;
240 break;
241 case 'b':
242 batch_flag = 1;
243 break;
244 default:
245 printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag);
246 ++argerr;
247 break;
248 }
249 }
250
251 if (optind < argc) {
252 printf("non-option ARGV-elements: ");
253 while (optind < argc)
254 printf("%s ", argv[optind++]);
255 printf("\n");
256 crm_help('?', EX_USAGE);
257 }
258
259 if (optind > argc) {
260 ++argerr;
261 }
262
263 if (argerr) {
264 crm_help('?', EX_USAGE);
265 }
266
267 if (command == 'w') {
268
269 const char *local = getenv("CIB_shadow");
270
271 if (local == NULL) {
272 fprintf(stderr, "No shadow instance provided\n");
273 rc = -ENXIO;
274 goto done;
275 }
276 fprintf(stdout, "%s\n", local);
277 rc = 0;
278 goto done;
279 }
280
281 if (shadow == NULL) {
282 fprintf(stderr, "No shadow instance provided\n");
283 fflush(stderr);
284 rc = -EINVAL;
285 goto done;
286
287 } else if (command != 's' && command != 'c') {
288 const char *local = getenv("CIB_shadow");
289
290 if (local != NULL && safe_str_neq(local, shadow) && force_flag == FALSE) {
291 fprintf(stderr,
292 "The supplied shadow instance (%s) is not the same as the active one (%s).\n"
293 " To prevent accidental destruction of the cluster,"
294 " the --force flag is required in order to proceed.\n", shadow, local);
295 fflush(stderr);
296 rc = EX_USAGE;
297 goto done;
298 }
299 }
300
301 if (dangerous_cmd && force_flag == FALSE) {
302 fprintf(stderr, "The supplied command is considered dangerous."
303 " To prevent accidental destruction of the cluster,"
304 " the --force flag is required in order to proceed.\n");
305 fflush(stderr);
306 rc = EX_USAGE;
307 goto done;
308 }
309
310 shadow_file = get_shadow_file(shadow);
311 if (command == 'D') {
312
313 rc = stat(shadow_file, &buf);
314 if (rc == 0) {
315 rc = unlink(shadow_file);
316 if (rc != 0) {
317 fprintf(stderr, "Could not remove shadow instance '%s': %s\n", shadow,
318 strerror(errno));
319 goto done;
320 }
321 }
322
323 shadow_teardown(shadow);
324 goto done;
325
326 } else if (command == 'F') {
327 printf("%s\n", shadow_file);
328 rc = 0;
329 goto done;
330 }
331
332 if (command == 'd' || command == 'r' || command == 'c' || command == 'C') {
333 real_cib = cib_new_no_shadow();
334 rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command);
335 if (rc != pcmk_ok) {
336 fprintf(stderr, "Signon to CIB failed: %s\n", pcmk_strerror(rc));
337 goto done;
338 }
339 }
340
341 rc = stat(shadow_file, &buf);
342
343 if (command == 'e' || command == 'c') {
344 if (rc == 0 && force_flag == FALSE) {
345 fprintf(stderr, "A shadow instance '%s' already exists.\n"
346 " To prevent accidental destruction of the cluster,"
347 " the --force flag is required in order to proceed.\n", shadow);
348 rc = -ENOTUNIQ;
349 goto done;
350 }
351
352 } else if (rc != 0) {
353 fprintf(stderr, "Could not access shadow instance '%s': %s\n", shadow, strerror(errno));
354 rc = -ENXIO;
355 goto done;
356 }
357
358 rc = pcmk_ok;
359 if (command == 'c' || command == 'e' || command == 'r') {
360 xmlNode *output = NULL;
361
362
363 if (command == 'c' || command == 'r') {
364 rc = real_cib->cmds->query(real_cib, NULL, &output, command_options);
365 if (rc != pcmk_ok) {
366 fprintf(stderr, "Could not connect to the CIB: %s\n", pcmk_strerror(rc));
367 goto done;
368 }
369
370 } else {
371 output = createEmptyCib(0);
372 if(validation) {
373 crm_xml_add(output, XML_ATTR_VALIDATION, validation);
374 }
375 printf("Created new %s configuration\n",
376 crm_element_value(output, XML_ATTR_VALIDATION));
377 }
378
379 rc = write_xml_file(output, shadow_file, FALSE);
380 free_xml(output);
381
382 if (rc < 0) {
383 fprintf(stderr, "Could not %s the shadow instance '%s': %s\n",
384 command == 'r' ? "reset" : "create",
385 shadow, strerror(errno));
386 goto done;
387 }
388 shadow_setup(shadow, FALSE);
389 rc = pcmk_ok;
390
391 } else if (command == 'E') {
392 const char *err = NULL;
393 char *editor = getenv("EDITOR");
394
395 if (editor == NULL) {
396 fprintf(stderr, "No value for $EDITOR defined\n");
397 rc = -EINVAL;
398 goto done;
399 }
400
401 execlp(editor, "--", shadow_file, NULL);
402 err = strerror(errno);
403 fprintf(stderr, "Could not invoke $EDITOR (%s %s): %s\n", editor, shadow_file, err);
404 rc = -EINVAL;
405 goto done;
406
407 } else if (command == 's') {
408 shadow_setup(shadow, TRUE);
409 rc = 0;
410 goto done;
411
412 } else if (command == 'p') {
413
414 char *output_s = NULL;
415 xmlNode *output = filename2xml(shadow_file);
416
417 output_s = dump_xml_formatted(output);
418 printf("%s", output_s);
419
420 free(output_s);
421 free_xml(output);
422
423 } else if (command == 'd') {
424
425 xmlNode *diff = NULL;
426 xmlNode *old_config = NULL;
427 xmlNode *new_config = filename2xml(shadow_file);
428
429 rc = real_cib->cmds->query(real_cib, NULL, &old_config, command_options);
430
431 if (rc != pcmk_ok) {
432 fprintf(stderr, "Could not query the CIB: %s\n", pcmk_strerror(rc));
433 goto done;
434 }
435
436 xml_track_changes(new_config, NULL, new_config, FALSE);
437 xml_calculate_changes(old_config, new_config);
438
439 diff = xml_create_patchset(0, old_config, new_config, NULL, FALSE);
440
441 xml_log_changes(LOG_INFO, __FUNCTION__, new_config);
442 xml_accept_changes(new_config);
443
444 if (diff != NULL) {
445 xml_log_patchset(0, " ", diff);
446 rc = 1;
447 goto done;
448 }
449 rc = 0;
450 goto done;
451
452 } else if (command == 'C') {
453
454 xmlNode *input = filename2xml(shadow_file);
455
456 if (full_upload) {
457 rc = real_cib->cmds->replace(real_cib, NULL, input, command_options);
458 } else {
459 xmlNode *config = first_named_child(input, XML_CIB_TAG_CONFIGURATION);
460
461 rc = real_cib->cmds->replace(real_cib, XML_CIB_TAG_CONFIGURATION, config,
462 command_options);
463 }
464
465 if (rc != pcmk_ok) {
466 fprintf(stderr, "Could not commit shadow instance '%s' to the CIB: %s\n",
467 shadow, pcmk_strerror(rc));
468 goto done;
469 }
470 shadow_teardown(shadow);
471 free_xml(input);
472 }
473 done:
474 free(shadow_file);
475 free(shadow);
476 return crm_exit(rc);
477 }