root/tools/crm_shadow.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_shadow_prompt
  2. shadow_setup
  3. shadow_teardown
  4. main

   1 /*
   2  * Copyright 2004-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   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 #include <crm/common/xml.h>
  25 
  26 #include <crm/common/ipc.h>
  27 
  28 #include <crm/cib.h>
  29 #include <crm/cib/internal.h>
  30 
  31 static int command_options = cib_sync_call;
  32 static cib_t *real_cib = NULL;
  33 static int force_flag = 0;
  34 static int batch_flag = 0;
  35 
  36 static char *
  37 get_shadow_prompt(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
  38 {
  39     return crm_strdup_printf("shadow[%.40s] # ", name);
  40 }
  41 
  42 static void
  43 shadow_setup(char *name, gboolean do_switch)
     /* [previous][next][first][last][top][bottom][index][help] */
  44 {
  45     const char *prompt = getenv("PS1");
  46     const char *shell = getenv("SHELL");
  47     char *new_prompt = get_shadow_prompt(name);
  48 
  49     printf("Setting up shadow instance\n");
  50 
  51     if (pcmk__str_eq(new_prompt, prompt, pcmk__str_casei)) {
  52         /* nothing to do */
  53         goto done;
  54 
  55     } else if (batch_flag == FALSE && shell != NULL) {
  56         setenv("PS1", new_prompt, 1);
  57         setenv("CIB_shadow", name, 1);
  58         printf("Type Ctrl-D to exit the crm_shadow shell\n");
  59 
  60         if (strstr(shell, "bash")) {
  61             execl(shell, shell, "--norc", "--noprofile", NULL);
  62         } else {
  63             execl(shell, shell, NULL);
  64         }
  65 
  66     } else if (do_switch) {
  67         printf("To switch to the named shadow instance, paste the following into your shell:\n");
  68 
  69     } else {
  70         printf
  71             ("A new shadow instance was created.  To begin using it paste the following into your shell:\n");
  72     }
  73     printf("  CIB_shadow=%s ; export CIB_shadow\n", name);
  74 
  75   done:
  76     free(new_prompt);
  77 }
  78 
  79 static void
  80 shadow_teardown(char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
  81 {
  82     const char *prompt = getenv("PS1");
  83     char *our_prompt = get_shadow_prompt(name);
  84 
  85     if (prompt != NULL && strstr(prompt, our_prompt)) {
  86         printf("Now type Ctrl-D to exit the crm_shadow shell\n");
  87 
  88     } else {
  89         printf
  90             ("Please remember to unset the CIB_shadow variable by pasting the following into your shell:\n");
  91         printf("  unset CIB_shadow\n");
  92     }
  93     free(our_prompt);
  94 }
  95 
  96 static pcmk__cli_option_t long_options[] = {
  97     // long option, argument type, storage, short option, description, flags
  98     {
  99         "help", no_argument, NULL, '?',
 100         "\t\tThis text", pcmk__option_default
 101     },
 102     {
 103         "version", no_argument, NULL, '$',
 104         "\t\tVersion information", pcmk__option_default
 105     },
 106     {
 107         "verbose", no_argument, NULL, 'V',
 108         "\t\tIncrease debug output", pcmk__option_default
 109     },
 110     {
 111         "-spacer-", no_argument, NULL, '-',
 112         "\nQueries:", pcmk__option_default
 113     },
 114     {
 115         "which", no_argument, NULL, 'w',
 116         "\t\tIndicate the active shadow copy", pcmk__option_default
 117     },
 118     {
 119         "display", no_argument, NULL, 'p',
 120         "\t\tDisplay the contents of the active shadow copy",
 121         pcmk__option_default
 122     },
 123     {
 124         "edit", no_argument, NULL, 'E',
 125         "\t\tEdit the contents of the active shadow copy with your "
 126             "favorite $EDITOR",
 127         pcmk__option_default
 128     },
 129     {
 130         "diff", no_argument, NULL, 'd',
 131         "\t\tDisplay the changes in the active shadow copy\n",
 132         pcmk__option_default
 133     },
 134     {
 135         "file", no_argument, NULL, 'F',
 136         "\t\tDisplay the location of the active shadow copy file\n",
 137         pcmk__option_default
 138     },
 139     {
 140         "-spacer-", no_argument, NULL, '-',
 141         "\nCommands:", pcmk__option_default
 142     },
 143     {
 144         "create", required_argument, NULL, 'c',
 145         "\tCreate the named shadow copy of the active cluster configuration",
 146         pcmk__option_default
 147     },
 148     {
 149         "create-empty", required_argument, NULL, 'e',
 150         "Create the named shadow copy with an empty cluster configuration. "
 151             "Optional: --validate-with",
 152         pcmk__option_default
 153     },
 154     {
 155         "commit", required_argument, NULL, 'C',
 156         "\tUpload the contents of the named shadow copy to the cluster",
 157         pcmk__option_default
 158     },
 159     {
 160         "delete", required_argument, NULL, 'D',
 161         "\tDelete the contents of the named shadow copy", pcmk__option_default
 162     },
 163     {
 164         "reset", required_argument, NULL, 'r',
 165         "\tRecreate named shadow copy from the active cluster configuration",
 166         pcmk__option_default
 167     },
 168     {
 169         "switch", required_argument, NULL, 's',
 170         "\t(Advanced) Switch to the named shadow copy", pcmk__option_default
 171     },
 172     {
 173         "-spacer-", no_argument, NULL, '-',
 174         "\nAdditional Options:", pcmk__option_default
 175     },
 176     {
 177         "force", no_argument, NULL, 'f',
 178         "\t\t(Advanced) Force the action to be performed", pcmk__option_default
 179     },
 180     {
 181         "batch", no_argument, NULL, 'b',
 182         "\t\t(Advanced) Don't spawn a new shell", pcmk__option_default
 183     },
 184     {
 185         "all", no_argument, NULL, 'a',
 186         "\t\t(Advanced) Upload entire CIB, including status, with --commit",
 187         pcmk__option_default
 188     },
 189     {
 190         "validate-with", required_argument, NULL, 'v',
 191         "(Advanced) Create an older configuration version", pcmk__option_default
 192     },
 193     {
 194         "-spacer-", no_argument, NULL, '-',
 195         "\nExamples:", pcmk__option_paragraph
 196     },
 197     {
 198         "-spacer-", no_argument, NULL, '-',
 199         "Create a blank shadow configuration:", pcmk__option_paragraph
 200     },
 201     {
 202         "-spacer-", no_argument, NULL, '-',
 203         " crm_shadow --create-empty myShadow", pcmk__option_example
 204     },
 205     {
 206         "-spacer-", no_argument, NULL, '-',
 207         "Create a shadow configuration from the running cluster:",
 208         pcmk__option_paragraph
 209     },
 210     {
 211         "-spacer-", no_argument, NULL, '-',
 212         " crm_shadow --create myShadow", pcmk__option_example
 213     },
 214     {
 215         "-spacer-", no_argument, NULL, '-',
 216         "Display the current shadow configuration:", pcmk__option_paragraph
 217     },
 218     {
 219         "-spacer-", no_argument, NULL, '-',
 220         " crm_shadow --display", pcmk__option_example
 221     },
 222     {
 223         "-spacer-", no_argument, NULL, '-',
 224         "Discard the current shadow configuration (named myShadow):",
 225         pcmk__option_paragraph
 226     },
 227     {
 228         "-spacer-", no_argument, NULL, '-',
 229         " crm_shadow --delete myShadow --force", pcmk__option_example
 230     },
 231     {
 232         "-spacer-", no_argument, NULL, '-',
 233         "Upload current shadow configuration (named myShadow) "
 234             "to running cluster:",
 235         pcmk__option_paragraph
 236     },
 237     {
 238         "-spacer-", no_argument, NULL, '-',
 239         " crm_shadow --commit myShadow", pcmk__option_example
 240     },
 241     { 0, 0, 0, 0 }
 242 };
 243 
 244 int
 245 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 246 {
 247     int rc = pcmk_ok;
 248     int flag;
 249     int argerr = 0;
 250     crm_exit_t exit_code = CRM_EX_OK;
 251     static int command = '?';
 252     const char *validation = NULL;
 253     char *shadow = NULL;
 254     char *shadow_file = NULL;
 255     gboolean full_upload = FALSE;
 256     gboolean dangerous_cmd = FALSE;
 257     struct stat buf;
 258     int option_index = 0;
 259 
 260     pcmk__cli_init_logging("crm_shadow", 0);
 261     pcmk__set_cli_options(NULL, "<query>|<command> [options]", long_options,
 262                           "perform Pacemaker configuration changes in a sandbox"
 263                           "\n\nThis command sets up an environment in which "
 264                           "configuration tools (cibadmin,\ncrm_resource, "
 265                           "etc.) work offline instead of against a live "
 266                           "cluster, allowing\nchanges to be previewed and "
 267                           "tested for side-effects.\n");
 268 
 269     if (argc < 2) {
 270         pcmk__cli_help('?', CRM_EX_USAGE);
 271     }
 272 
 273     while (1) {
 274         flag = pcmk__next_cli_option(argc, argv, &option_index, NULL);
 275         if (flag == -1 || flag == 0)
 276             break;
 277 
 278         switch (flag) {
 279             case 'a':
 280                 full_upload = TRUE;
 281                 break;
 282             case 'd':
 283             case 'E':
 284             case 'p':
 285             case 'w':
 286             case 'F':
 287                 command = flag;
 288                 free(shadow);
 289                 shadow = NULL;
 290                 {
 291                     const char *env = getenv("CIB_shadow");
 292                     if(env) {
 293                         shadow = strdup(env);
 294                     } else {
 295                         fprintf(stderr, "No active shadow configuration defined\n");
 296                         crm_exit(CRM_EX_NOSUCH);
 297                     }
 298                 }
 299                 break;
 300             case 'v':
 301                 validation = optarg;
 302                 break;
 303             case 'e':
 304             case 'c':
 305             case 's':
 306             case 'r':
 307                 command = flag;
 308                 pcmk__str_update(&shadow, optarg);
 309                 break;
 310             case 'C':
 311             case 'D':
 312                 command = flag;
 313                 dangerous_cmd = TRUE;
 314                 pcmk__str_update(&shadow, optarg);
 315                 break;
 316             case 'V':
 317                 command_options = command_options | cib_verbose;
 318                 crm_bump_log_level(argc, argv);
 319                 break;
 320             case '$':
 321             case '?':
 322                 pcmk__cli_help(flag, CRM_EX_OK);
 323                 break;
 324             case 'f':
 325                 cib__set_call_options(command_options, crm_system_name,
 326                                       cib_quorum_override);
 327                 force_flag = 1;
 328                 break;
 329             case 'b':
 330                 batch_flag = 1;
 331                 break;
 332             default:
 333                 printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag);
 334                 ++argerr;
 335                 break;
 336         }
 337     }
 338 
 339     if (optind < argc) {
 340         printf("non-option ARGV-elements: ");
 341         while (optind < argc)
 342             printf("%s ", argv[optind++]);
 343         printf("\n");
 344         pcmk__cli_help('?', CRM_EX_USAGE);
 345     }
 346 
 347     if (optind > argc) {
 348         ++argerr;
 349     }
 350 
 351     if (argerr) {
 352         pcmk__cli_help('?', CRM_EX_USAGE);
 353     }
 354 
 355     if (command == 'w') {
 356         /* which shadow instance is active? */
 357         const char *local = getenv("CIB_shadow");
 358 
 359         if (local == NULL) {
 360             fprintf(stderr, "No shadow instance provided\n");
 361             exit_code = CRM_EX_NOSUCH;
 362         } else {
 363             fprintf(stdout, "%s\n", local);
 364         }
 365         goto done;
 366     }
 367 
 368     if (shadow == NULL) {
 369         fprintf(stderr, "No shadow instance provided\n");
 370         fflush(stderr);
 371         exit_code = CRM_EX_NOSUCH;
 372         goto done;
 373 
 374     } else if (command != 's' && command != 'c') {
 375         const char *local = getenv("CIB_shadow");
 376 
 377         if (local != NULL && !pcmk__str_eq(local, shadow, pcmk__str_casei) && force_flag == FALSE) {
 378             fprintf(stderr,
 379                     "The supplied shadow instance (%s) is not the same as the active one (%s).\n"
 380                     "  To prevent accidental destruction of the cluster,"
 381                     " the --force flag is required in order to proceed.\n", shadow, local);
 382             fflush(stderr);
 383             exit_code = CRM_EX_USAGE;
 384             goto done;
 385         }
 386     }
 387 
 388     if (dangerous_cmd && force_flag == FALSE) {
 389         fprintf(stderr, "The supplied command is considered dangerous."
 390                 "  To prevent accidental destruction of the cluster,"
 391                 " the --force flag is required in order to proceed.\n");
 392         fflush(stderr);
 393         exit_code = CRM_EX_USAGE;
 394         goto done;
 395     }
 396 
 397     shadow_file = get_shadow_file(shadow);
 398     if (command == 'D') {
 399         /* delete the file */
 400         if ((unlink(shadow_file) < 0) && (errno != ENOENT)) {
 401             exit_code = crm_errno2exit(errno);
 402             fprintf(stderr, "Could not remove shadow instance '%s': %s\n",
 403                     shadow, strerror(errno));
 404         }
 405         shadow_teardown(shadow);
 406         goto done;
 407 
 408     } else if (command == 'F') {
 409         printf("%s\n", shadow_file);
 410         goto done;
 411     }
 412 
 413     if (command == 'd' || command == 'r' || command == 'c' || command == 'C') {
 414         real_cib = cib_new_no_shadow();
 415         rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command);
 416         if (rc != pcmk_ok) {
 417             fprintf(stderr, "Could not connect to CIB: %s\n",
 418                     pcmk_strerror(rc));
 419             exit_code = crm_errno2exit(rc);
 420             goto done;
 421         }
 422     }
 423 
 424     // File existence check
 425     rc = stat(shadow_file, &buf);
 426     if (command == 'e' || command == 'c') {
 427         if (rc == 0 && force_flag == FALSE) {
 428             fprintf(stderr, "A shadow instance '%s' already exists.\n"
 429                     "  To prevent accidental destruction of the cluster,"
 430                     " the --force flag is required in order to proceed.\n", shadow);
 431             exit_code = CRM_EX_CANTCREAT;
 432             goto done;
 433         }
 434     } else if (rc < 0) {
 435         fprintf(stderr, "Could not access shadow instance '%s': %s\n", shadow, strerror(errno));
 436         exit_code = CRM_EX_NOSUCH;
 437         goto done;
 438     }
 439 
 440     if (command == 'c' || command == 'e' || command == 'r') {
 441         xmlNode *output = NULL;
 442 
 443         /* create a shadow instance based on the current cluster config */
 444         if (command == 'c' || command == 'r') {
 445             rc = real_cib->cmds->query(real_cib, NULL, &output, command_options);
 446             if (rc != pcmk_ok) {
 447                 fprintf(stderr, "Could not connect to the CIB manager: %s\n",
 448                         pcmk_strerror(rc));
 449                 exit_code = crm_errno2exit(rc);
 450                 goto done;
 451             }
 452 
 453         } else {
 454             output = createEmptyCib(0);
 455             if(validation) {
 456                 crm_xml_add(output, XML_ATTR_VALIDATION, validation);
 457             }
 458             printf("Created new %s configuration\n",
 459                    crm_element_value(output, XML_ATTR_VALIDATION));
 460         }
 461 
 462         rc = write_xml_file(output, shadow_file, FALSE);
 463         free_xml(output);
 464 
 465         if (rc < 0) {
 466             fprintf(stderr, "Could not %s the shadow instance '%s': %s\n",
 467                     command == 'r' ? "reset" : "create",
 468                     shadow, pcmk_strerror(rc));
 469             exit_code = crm_errno2exit(rc);
 470             goto done;
 471         }
 472         shadow_setup(shadow, FALSE);
 473 
 474     } else if (command == 'E') {
 475         char *editor = getenv("EDITOR");
 476 
 477         if (editor == NULL) {
 478             fprintf(stderr, "No value for EDITOR defined\n");
 479             exit_code = CRM_EX_NOT_CONFIGURED;
 480             goto done;
 481         }
 482 
 483         execlp(editor, "--", shadow_file, NULL);
 484         fprintf(stderr, "Could not invoke EDITOR (%s %s): %s\n",
 485                 editor, shadow_file, strerror(errno));
 486         exit_code = CRM_EX_OSFILE;
 487         goto done;
 488 
 489     } else if (command == 's') {
 490         shadow_setup(shadow, TRUE);
 491         goto done;
 492 
 493     } else if (command == 'p') {
 494         /* display the current contents */
 495         char *output_s = NULL;
 496         xmlNode *output = filename2xml(shadow_file);
 497 
 498         output_s = dump_xml_formatted(output);
 499         printf("%s", output_s);
 500 
 501         free(output_s);
 502         free_xml(output);
 503 
 504     } else if (command == 'd') {
 505         /* diff against cluster */
 506         xmlNode *diff = NULL;
 507         xmlNode *old_config = NULL;
 508         xmlNode *new_config = filename2xml(shadow_file);
 509 
 510         rc = real_cib->cmds->query(real_cib, NULL, &old_config, command_options);
 511 
 512         if (rc != pcmk_ok) {
 513             fprintf(stderr, "Could not query the CIB: %s\n", pcmk_strerror(rc));
 514             exit_code = crm_errno2exit(rc);
 515             goto done;
 516         }
 517 
 518         xml_track_changes(new_config, NULL, new_config, FALSE);
 519         xml_calculate_changes(old_config, new_config);
 520 
 521         diff = xml_create_patchset(0, old_config, new_config, NULL, FALSE);
 522 
 523         xml_log_changes(LOG_INFO, __func__, new_config);
 524         xml_accept_changes(new_config);
 525 
 526         if (diff != NULL) {
 527             xml_log_patchset(LOG_STDOUT, "  ", diff);
 528             exit_code = CRM_EX_ERROR;
 529         }
 530         goto done;
 531 
 532     } else if (command == 'C') {
 533         /* commit to the cluster */
 534         xmlNode *input = filename2xml(shadow_file);
 535         xmlNode *section_xml = input;
 536         const char *section = NULL;
 537 
 538         if (!full_upload) {
 539             section = XML_CIB_TAG_CONFIGURATION;
 540             section_xml = first_named_child(input, section);
 541         }
 542         rc = real_cib->cmds->replace(real_cib, section, section_xml,
 543                                      command_options);
 544         if (rc != pcmk_ok) {
 545             fprintf(stderr, "Could not commit shadow instance '%s' to the CIB: %s\n",
 546                     shadow, pcmk_strerror(rc));
 547             exit_code = crm_errno2exit(rc);
 548         }
 549         shadow_teardown(shadow);
 550         free_xml(input);
 551     }
 552   done:
 553     free(shadow_file);
 554     free(shadow);
 555     crm_exit(exit_code);
 556 }

/* [previous][next][first][last][top][bottom][index][help] */