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-2020 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     crm_log_cli_init("crm_shadow");
 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                 free(shadow);
 309                 shadow = strdup(optarg);
 310                 break;
 311             case 'C':
 312             case 'D':
 313                 command = flag;
 314                 dangerous_cmd = TRUE;
 315                 free(shadow);
 316                 shadow = strdup(optarg);
 317                 break;
 318             case 'V':
 319                 command_options = command_options | cib_verbose;
 320                 crm_bump_log_level(argc, argv);
 321                 break;
 322             case '$':
 323             case '?':
 324                 pcmk__cli_help(flag, CRM_EX_OK);
 325                 break;
 326             case 'f':
 327                 cib__set_call_options(command_options, crm_system_name,
 328                                       cib_quorum_override);
 329                 force_flag = 1;
 330                 break;
 331             case 'b':
 332                 batch_flag = 1;
 333                 break;
 334             default:
 335                 printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag);
 336                 ++argerr;
 337                 break;
 338         }
 339     }
 340 
 341     if (optind < argc) {
 342         printf("non-option ARGV-elements: ");
 343         while (optind < argc)
 344             printf("%s ", argv[optind++]);
 345         printf("\n");
 346         pcmk__cli_help('?', CRM_EX_USAGE);
 347     }
 348 
 349     if (optind > argc) {
 350         ++argerr;
 351     }
 352 
 353     if (argerr) {
 354         pcmk__cli_help('?', CRM_EX_USAGE);
 355     }
 356 
 357     if (command == 'w') {
 358         /* which shadow instance is active? */
 359         const char *local = getenv("CIB_shadow");
 360 
 361         if (local == NULL) {
 362             fprintf(stderr, "No shadow instance provided\n");
 363             exit_code = CRM_EX_NOSUCH;
 364         } else {
 365             fprintf(stdout, "%s\n", local);
 366         }
 367         goto done;
 368     }
 369 
 370     if (shadow == NULL) {
 371         fprintf(stderr, "No shadow instance provided\n");
 372         fflush(stderr);
 373         exit_code = CRM_EX_NOSUCH;
 374         goto done;
 375 
 376     } else if (command != 's' && command != 'c') {
 377         const char *local = getenv("CIB_shadow");
 378 
 379         if (local != NULL && !pcmk__str_eq(local, shadow, pcmk__str_casei) && force_flag == FALSE) {
 380             fprintf(stderr,
 381                     "The supplied shadow instance (%s) is not the same as the active one (%s).\n"
 382                     "  To prevent accidental destruction of the cluster,"
 383                     " the --force flag is required in order to proceed.\n", shadow, local);
 384             fflush(stderr);
 385             exit_code = CRM_EX_USAGE;
 386             goto done;
 387         }
 388     }
 389 
 390     if (dangerous_cmd && force_flag == FALSE) {
 391         fprintf(stderr, "The supplied command is considered dangerous."
 392                 "  To prevent accidental destruction of the cluster,"
 393                 " the --force flag is required in order to proceed.\n");
 394         fflush(stderr);
 395         exit_code = CRM_EX_USAGE;
 396         goto done;
 397     }
 398 
 399     shadow_file = get_shadow_file(shadow);
 400     if (command == 'D') {
 401         /* delete the file */
 402         if ((unlink(shadow_file) < 0) && (errno != ENOENT)) {
 403             exit_code = crm_errno2exit(errno);
 404             fprintf(stderr, "Could not remove shadow instance '%s': %s\n",
 405                     shadow, strerror(errno));
 406         }
 407         shadow_teardown(shadow);
 408         goto done;
 409 
 410     } else if (command == 'F') {
 411         printf("%s\n", shadow_file);
 412         goto done;
 413     }
 414 
 415     if (command == 'd' || command == 'r' || command == 'c' || command == 'C') {
 416         real_cib = cib_new_no_shadow();
 417         rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command);
 418         if (rc != pcmk_ok) {
 419             fprintf(stderr, "Could not connect to CIB: %s\n",
 420                     pcmk_strerror(rc));
 421             exit_code = crm_errno2exit(rc);
 422             goto done;
 423         }
 424     }
 425 
 426     // File existence check
 427     rc = stat(shadow_file, &buf);
 428     if (command == 'e' || command == 'c') {
 429         if (rc == 0 && force_flag == FALSE) {
 430             fprintf(stderr, "A shadow instance '%s' already exists.\n"
 431                     "  To prevent accidental destruction of the cluster,"
 432                     " the --force flag is required in order to proceed.\n", shadow);
 433             exit_code = CRM_EX_CANTCREAT;
 434             goto done;
 435         }
 436     } else if (rc < 0) {
 437         fprintf(stderr, "Could not access shadow instance '%s': %s\n", shadow, strerror(errno));
 438         exit_code = CRM_EX_NOSUCH;
 439         goto done;
 440     }
 441 
 442     if (command == 'c' || command == 'e' || command == 'r') {
 443         xmlNode *output = NULL;
 444 
 445         /* create a shadow instance based on the current cluster config */
 446         if (command == 'c' || command == 'r') {
 447             rc = real_cib->cmds->query(real_cib, NULL, &output, command_options);
 448             if (rc != pcmk_ok) {
 449                 fprintf(stderr, "Could not connect to the CIB manager: %s\n",
 450                         pcmk_strerror(rc));
 451                 exit_code = crm_errno2exit(rc);
 452                 goto done;
 453             }
 454 
 455         } else {
 456             output = createEmptyCib(0);
 457             if(validation) {
 458                 crm_xml_add(output, XML_ATTR_VALIDATION, validation);
 459             }
 460             printf("Created new %s configuration\n",
 461                    crm_element_value(output, XML_ATTR_VALIDATION));
 462         }
 463 
 464         rc = write_xml_file(output, shadow_file, FALSE);
 465         free_xml(output);
 466 
 467         if (rc < 0) {
 468             fprintf(stderr, "Could not %s the shadow instance '%s': %s\n",
 469                     command == 'r' ? "reset" : "create",
 470                     shadow, pcmk_strerror(rc));
 471             exit_code = crm_errno2exit(rc);
 472             goto done;
 473         }
 474         shadow_setup(shadow, FALSE);
 475 
 476     } else if (command == 'E') {
 477         char *editor = getenv("EDITOR");
 478 
 479         if (editor == NULL) {
 480             fprintf(stderr, "No value for EDITOR defined\n");
 481             exit_code = CRM_EX_NOT_CONFIGURED;
 482             goto done;
 483         }
 484 
 485         execlp(editor, "--", shadow_file, NULL);
 486         fprintf(stderr, "Could not invoke EDITOR (%s %s): %s\n",
 487                 editor, shadow_file, strerror(errno));
 488         exit_code = CRM_EX_OSFILE;
 489         goto done;
 490 
 491     } else if (command == 's') {
 492         shadow_setup(shadow, TRUE);
 493         goto done;
 494 
 495     } else if (command == 'p') {
 496         /* display the current contents */
 497         char *output_s = NULL;
 498         xmlNode *output = filename2xml(shadow_file);
 499 
 500         output_s = dump_xml_formatted(output);
 501         printf("%s", output_s);
 502 
 503         free(output_s);
 504         free_xml(output);
 505 
 506     } else if (command == 'd') {
 507         /* diff against cluster */
 508         xmlNode *diff = NULL;
 509         xmlNode *old_config = NULL;
 510         xmlNode *new_config = filename2xml(shadow_file);
 511 
 512         rc = real_cib->cmds->query(real_cib, NULL, &old_config, command_options);
 513 
 514         if (rc != pcmk_ok) {
 515             fprintf(stderr, "Could not query the CIB: %s\n", pcmk_strerror(rc));
 516             exit_code = crm_errno2exit(rc);
 517             goto done;
 518         }
 519 
 520         xml_track_changes(new_config, NULL, new_config, FALSE);
 521         xml_calculate_changes(old_config, new_config);
 522 
 523         diff = xml_create_patchset(0, old_config, new_config, NULL, FALSE);
 524 
 525         xml_log_changes(LOG_INFO, __func__, new_config);
 526         xml_accept_changes(new_config);
 527 
 528         if (diff != NULL) {
 529             xml_log_patchset(LOG_STDOUT, "  ", diff);
 530             exit_code = CRM_EX_ERROR;
 531         }
 532         goto done;
 533 
 534     } else if (command == 'C') {
 535         /* commit to the cluster */
 536         xmlNode *input = filename2xml(shadow_file);
 537         xmlNode *section_xml = input;
 538         const char *section = NULL;
 539 
 540         if (!full_upload) {
 541             section = XML_CIB_TAG_CONFIGURATION;
 542             section_xml = first_named_child(input, section);
 543         }
 544         rc = real_cib->cmds->replace(real_cib, section, section_xml,
 545                                      command_options);
 546         if (rc != pcmk_ok) {
 547             fprintf(stderr, "Could not commit shadow instance '%s' to the CIB: %s\n",
 548                     shadow, pcmk_strerror(rc));
 549             exit_code = crm_errno2exit(rc);
 550         }
 551         shadow_teardown(shadow);
 552         free_xml(input);
 553     }
 554   done:
 555     free(shadow_file);
 556     free(shadow);
 557     crm_exit(exit_code);
 558 }

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