summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_cli/lib
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbitmq_cli/lib')
-rw-r--r--deps/rabbitmq_cli/lib/rabbit_common/records.ex19
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/auto_complete.ex118
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/command_behaviour.ex170
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_default_switches_and_timeout.ex16
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_no_positional_arguments.ex21
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positional_argument.ex25
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positive_integer_argument.ex34
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_two_positional_arguments.ex25
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/alarms.ex88
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/ansi.ex35
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/code_path.ex108
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/command_modules.ex196
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/config.ex200
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/data_coercion.ex21
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/distribution.ex138
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/doc_guide.ex67
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/erl_eval.ex26
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/exit_codes.ex56
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/feature_flags.ex19
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/helpers.ex148
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/input.ex39
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/listeners.ex312
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/log_files.ex52
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/memory.ex105
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_default_virtual_host.ex15
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_no_defaults.ex15
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/networking.ex73
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex198
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/os_pid.ex56
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/output.ex72
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/parser.ex311
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/paths.ex55
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/platform.ex37
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_running.ex17
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_stopped.ex17
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/validators.ex115
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/core/version.ex24
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_user_command.ex98
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_vhost_command.ex62
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/authenticate_user_command.ex79
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/autocomplete_command.ex53
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_online_nodes_command.ex62
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_startup_command.ex45
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cancel_sync_queue_command.ex52
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_cluster_node_type_command.ex87
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_password_command.ex76
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_global_parameter_command.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_operator_policy_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_parameter_command.ex60
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_password_command.ex46
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_permissions_command.ex61
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_policy_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_topic_permissions_command.ex85
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_user_limits_command.ex50
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_vhost_limits_command.ex43
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_all_connections_command.ex124
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_connection_command.ex46
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cluster_status_command.ex285
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex116
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_queue_command.ex125
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_user_command.ex52
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_vhost_command.ex41
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/enable_feature_flag_command.ex60
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex102
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/environment_command.ex38
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_command.ex107
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_file_command.ex75
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/exec_command.ex80
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/export_definitions_command.ex143
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_boot_command.ex57
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_gc_command.ex35
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_reset_command.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/forget_cluster_node_command.ex122
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex343
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex98
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/import_definitions_command.ex136
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/join_cluster_command.ex95
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_bindings_command.ex74
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex85
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_ciphers_command.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex84
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_consumers_command.ex108
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_exchanges_command.ex67
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_feature_flags_command.ex94
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_global_parameters_command.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_hashes_command.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_operator_policies_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_parameters_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_permissions_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_policies_command.ex49
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex143
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_topic_permissions_command.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex95
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_limits_command.ex91
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_permissions_command.ex58
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_topic_permissions_command.ex54
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_users_command.ex43
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhost_limits_command.ex90
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhosts_command.ex84
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/node_health_check_command.ex87
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/ping_command.ex90
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/purge_queue_command.ex73
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rename_cluster_node_command.ex108
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/report_command.ex118
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/reset_command.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/restart_vhost_command.ex62
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/resume_listeners_command.ex45
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rotate_logs_command.ex34
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_cluster_name_command.ex47
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_disk_free_limit_command.ex140
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_global_parameter_command.ex57
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_log_level_command.ex74
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_operator_policy_command.ex79
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_parameter_command.ex68
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_command.ex78
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_policy_command.ex76
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_topic_permissions_command.ex75
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_limits_command.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_tags_command.ex61
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vhost_limits_command.ex50
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vm_memory_high_watermark_command.ex146
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex106
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/start_app_command.ex25
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/status_command.ex253
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_app_command.ex26
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_command.ex72
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/suspend_listeners_command.ex46
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/sync_queue_command.ex54
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_off_command.ex42
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_on_command.ex42
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/update_cluster_nodes_command.ex62
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/version_command.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/wait_command.ex269
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex62
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/rpc_stream.ex124
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/default_output.ex94
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/alarms_command.ex77
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/certificates_command.ex55
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_alarms_command.ex86
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration_command.ex101
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_local_alarms_command.ex85
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_connectivity_command.ex119
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_listener_command.ex82
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_protocol_listener_command.ex90
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_running_command.ex46
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_virtual_hosts_command.ex71
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/cipher_suites_command.ex122
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/command_line_arguments_command.ex41
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/consume_event_stream_command.ex71
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/disable_auth_attempt_source_tracking_command.ex35
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/discover_peers_command.ex36
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/enable_auth_attempt_source_tracking_command.ex36
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_hash_command.ex35
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_sources_command.ex116
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_version_command.ex72
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_booting_command.ex53
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_running_command.ex45
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_network_interfaces_command.ex77
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_node_auth_attempt_stats_command.ex75
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/listeners_command.ex92
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_location_command.ex56
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_command.ex50
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_stream_command.ex73
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/maybe_stuck_command.ex29
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/memory_breakdown_command.ex103
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/observer_command.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/os_env_command.ex67
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/reset_node_auth_attempt_metrics_command.ex37
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolve_hostname_command.ex94
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolver_info_command.ex84
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/runtime_thread_stats_command.ex70
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/schema_info_command.ex73
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/server_version_command.ex37
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/tls_versions_command.ex39
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/diagnostics_helpers.ex38
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatter_behaviour.ex42
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/csv.ex127
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/erlang.ex18
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/formatter_helpers.ex182
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/inspect.ex40
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json.ex69
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json_stream.ex75
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/msacc.ex19
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/plugins.ex247
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/pretty_table.ex87
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/report.ex52
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string.ex26
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string_per_line.ex42
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/table.ex138
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/information_unit.ex68
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/directories_command.ex134
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/disable_command.ex146
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/enable_command.ex156
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/is_enabled.ex154
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/list_command.ex188
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/set_command.ex133
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/error_output.ex55
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/plugins_helpers.ex232
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/printer_behaviour.ex21
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/printers/file.ex36
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io.ex28
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io_raw.ex28
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/add_member_command.ex70
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_mirror_sync_critical_command.ex105
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_quorum_critical_command.ex118
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/delete_member_command.ex58
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/grow_command.ex126
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/peek_command.ex112
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/quorum_status_command.ex54
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/rebalance_command.ex84
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/reclaim_quorum_memory_command.ex69
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/shrink_command.ex97
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/add_replica_command.ex64
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/delete_replica_command.ex61
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/set_stream_retention_policy_command.ex58
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/time_unit.ex48
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_quorum_plus_one_command.ex65
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_synchronized_mirror_command.ex65
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/drain_command.ex54
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/post_upgrade_command.ex39
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/revive_command.ex56
-rw-r--r--deps/rabbitmq_cli/lib/rabbitmqctl.ex620
222 files changed, 18165 insertions, 0 deletions
diff --git a/deps/rabbitmq_cli/lib/rabbit_common/records.ex b/deps/rabbitmq_cli/lib/rabbit_common/records.ex
new file mode 100644
index 0000000000..dc1503caf7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbit_common/records.ex
@@ -0,0 +1,19 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitCommon.Records do
+ require Record
+ import Record, only: [defrecord: 2, extract: 2]
+
+ # Important: amqqueue records must not be used directly since they are versioned
+ # for mixed version cluster compatibility. Convert records
+ # to maps on the server end to access the fields of those records. MK.
+ defrecord :listener, extract(:listener, from_lib: "rabbit_common/include/rabbit.hrl")
+ defrecord :plugin, extract(:plugin, from_lib: "rabbit_common/include/rabbit.hrl")
+ defrecord :resource, extract(:resource, from_lib: "rabbit_common/include/rabbit.hrl")
+
+ defrecord :hostent, extract(:hostent, from_lib: "kernel/include/inet.hrl")
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/auto_complete.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/auto_complete.ex
new file mode 100644
index 0000000000..1f907b528d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/auto_complete.ex
@@ -0,0 +1,118 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.AutoComplete do
+ alias RabbitMQ.CLI.Core.{CommandModules, Parser}
+
+ # Use the same jaro distance limit as in Elixir's "did you mean?"
+ @jaro_distance_limit 0.77
+
+ @spec complete(String.t(), [String.t()]) :: [String.t()]
+ def complete(_, []) do
+ []
+ end
+
+ def complete(script_name, args) do
+ case Parser.parse_global(args) do
+ {_, %{script_name: _args_script_name}, _} ->
+ complete(args)
+
+ _ ->
+ complete(["--script-name", script_name | args])
+ end
+ end
+
+ def suggest_command(_cmd_name, empty) when empty == %{} do
+ nil
+ end
+ def suggest_command(typed, module_map) do
+ suggestion =
+ module_map
+ |> Map.keys()
+ |> Enum.map(fn existing ->
+ {existing, String.jaro_distance(existing, typed)}
+ end)
+ |> Enum.max_by(fn {_, distance} -> distance end)
+
+ case suggestion do
+ {cmd, distance} when distance >= @jaro_distance_limit ->
+ {:suggest, cmd}
+ _ ->
+ nil
+ end
+ end
+
+ defp complete(tokens) do
+ {command, command_name, _, _, _} = Parser.parse(tokens)
+ last_token = List.last(tokens)
+
+ case {command, command_name} do
+ ## No command provided
+ {_, ""} ->
+ complete_default_opts(last_token)
+
+ ## Command is not found/incomplete
+ {:no_command, command_name} ->
+ complete_command_name(command_name)
+
+ {{:suggest, _}, command_name} ->
+ complete_command_name(command_name)
+
+ ## Command is found
+ {command, _} ->
+ complete_command_opts(command, last_token)
+ end
+ |> Enum.sort()
+ end
+
+ defp complete_default_opts(opt) do
+ Parser.default_switches()
+ |> Keyword.keys()
+ |> Enum.map(fn sw -> "--" <> to_string(sw) end)
+ |> select_starts_with(opt)
+ |> format_options
+ end
+
+ defp complete_command_name(command_name) do
+ module_map = CommandModules.module_map()
+
+ case module_map[command_name] do
+ nil ->
+ module_map
+ |> Map.keys()
+ |> select_starts_with(command_name)
+
+ _ ->
+ command_name
+ end
+ end
+
+ defp complete_command_opts(command, <<"-", _::binary>> = opt) do
+ switches =
+ command.switches
+ |> Keyword.keys()
+ |> Enum.map(fn sw -> "--" <> to_string(sw) end)
+
+ # aliases = command.aliases
+ # |> Keyword.keys
+ # |> Enum.map(fn(al) -> "-" <> to_string(al) end)
+ select_starts_with(switches, opt)
+ |> format_options
+ end
+
+ defp complete_command_opts(_, _) do
+ []
+ end
+
+ defp select_starts_with(list, prefix) do
+ Enum.filter(list, fn el -> String.starts_with?(el, prefix) end)
+ end
+
+ defp format_options(options) do
+ options
+ |> Enum.map(fn opt -> String.replace(opt, "_", "-") end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/command_behaviour.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/command_behaviour.ex
new file mode 100644
index 0000000000..800d4d3227
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/command_behaviour.ex
@@ -0,0 +1,170 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.CommandBehaviour do
+ alias RabbitMQ.CLI.Core.Helpers
+
+ @type pair_of_strings :: nonempty_list(String.t())
+
+ @callback usage() :: String.t() | [String.t()]
+ # validates CLI arguments
+ @callback validate(list(), map()) :: :ok | {:validation_failure, atom() | {atom(), String.t()}}
+ @callback merge_defaults(list(), map()) :: {list(), map()}
+ @callback banner(list(), map()) :: [String.t()] | String.t() | nil
+ @callback run(list(), map()) :: any
+ # Coerces run/2 return value into the standard command output form
+ # that is then formatted, printed and returned as an exit code.
+ # There is a default implementation for this callback in DefaultOutput module
+ @callback output(any, map()) ::
+ :ok
+ | {:ok, any}
+ | {:stream, Enum.t()}
+ | {:error, RabbitMQ.CLI.Core.ExitCodes.exit_code(), [String.t()]}
+
+ @optional_callbacks formatter: 0,
+ printer: 0,
+ scopes: 0,
+ usage_additional: 0,
+ usage_doc_guides: 0,
+ description: 0,
+ help_section: 0,
+ switches: 0,
+ aliases: 0,
+ # validates execution environment, e.g. file presence,
+ # whether RabbitMQ is in an expected state on a node, etc
+ validate_execution_environment: 2,
+ distribution: 1
+
+ @callback validate_execution_environment(list(), map()) ::
+ :ok | {:validation_failure, atom() | {atom(), any}}
+ @callback switches() :: Keyword.t()
+ @callback aliases() :: Keyword.t()
+
+ @callback formatter() :: atom()
+ @callback printer() :: atom()
+ @callback scopes() :: [atom()] | nil
+ @callback description() :: String.t()
+ @callback help_section() :: String.t()
+ @callback usage_additional() :: String.t() | [String.t()] | nonempty_list(pair_of_strings()) | [{String.t(), String.t()}]
+ @callback usage_doc_guides() :: String.t() | [String.t()]
+ ## Erlang distribution control
+ ## :cli - default rabbitmqctl generated node name
+ ## :none - disable erlang distribution
+ ## {:fun, fun} - use a custom function to start distribution
+ @callback distribution(map()) :: :cli | :none | {:fun, (map() -> :ok | {:error, any()})}
+
+ defmacro defcmd(map) do
+ usage_q = case map[:usage] do
+ nil -> :ok
+ usage ->
+ quote do def usage(), do: unquote(usage) end
+ end
+ scopes_q = case map[:scopes] do
+ nil -> :ok
+ scopes ->
+ quote do def scopes(), do: unquote(scopes) end
+ end
+ description_q = case map[:description] do
+ nil -> :ok
+ description ->
+ quote do def description(), do: unquote(description) end
+ end
+ help_section_q = case map[:help_section] do
+ nil -> :ok
+ help_section ->
+ quote do def help_section(), do: unquote(help_section) end
+ end
+ usage_additional_q = case map[:usage_additional] do
+ nil -> :ok
+ usage_additional ->
+ quote do def usage_additional(), do: unquote(usage_additional) end
+ end
+ formatter_q = case map[:formatter] do
+ nil -> :ok
+ formatter ->
+ quote do def formatter(), do: unquote(formatter) end
+ end
+ switches_q = case map[:switches] do
+ nil -> :ok
+ switches ->
+ quote do def switches(), do: unquote(switches) end
+ end
+ aliases_q = case map[:aliases] do
+ nil -> :ok
+ aliases ->
+ quote do def aliases(), do: unquote(aliases) end
+ end
+
+ quote do
+ unquote(usage_q)
+ unquote(scopes_q)
+ unquote(description_q)
+ unquote(help_section_q)
+ unquote(usage_additional_q)
+ unquote(formatter_q)
+ unquote(switches_q)
+ unquote(aliases_q)
+ end
+ end
+
+ def usage(cmd) do
+ cmd.usage()
+ end
+
+ def scopes(cmd) do
+ Helpers.apply_if_exported(cmd, :scopes, [], nil)
+ end
+
+ def description(cmd) do
+ Helpers.apply_if_exported(cmd, :description, [], "")
+ end
+
+ def help_section(cmd) do
+ case Helpers.apply_if_exported(cmd, :help_section, [], :other) do
+ :other ->
+ case Application.get_application(cmd) do
+ :rabbitmqctl -> :other
+ plugin -> {:plugin, plugin}
+ end
+ section ->
+ section
+ end
+ end
+
+ def usage_additional(cmd) do
+ Helpers.apply_if_exported(cmd, :usage_additional, [], [])
+ end
+
+ def usage_doc_guides(cmd) do
+ Helpers.apply_if_exported(cmd, :usage_doc_guides, [], [])
+ end
+
+ def formatter(cmd, default) do
+ Helpers.apply_if_exported(cmd, :formatter, [], default)
+ end
+
+ def printer(cmd, default) do
+ Helpers.apply_if_exported(cmd, :printer, [], default)
+ end
+
+ def switches(cmd) do
+ Helpers.apply_if_exported(cmd, :switches, [], [])
+ end
+
+ def aliases(cmd) do
+ Helpers.apply_if_exported(cmd, :aliases, [], [])
+ end
+
+ def validate_execution_environment(cmd, args, options) do
+ Helpers.apply_if_exported(cmd,
+ :validate_execution_environment, [args, options],
+ :ok)
+ end
+
+ def distribution(cmd, options) do
+ Helpers.apply_if_exported(cmd, :distribution, [options], :cli)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_default_switches_and_timeout.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_default_switches_and_timeout.ex
new file mode 100644
index 0000000000..3a78974b0c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_default_switches_and_timeout.ex
@@ -0,0 +1,16 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout do
+ defmacro __using__(_) do
+ quote do
+ def switches(), do: [timeout: :integer]
+ def aliases(), do: [t: :timeout]
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_no_positional_arguments.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_no_positional_arguments.ex
new file mode 100644
index 0000000000..166c4f22f7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_no_positional_arguments.ex
@@ -0,0 +1,21 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.AcceptsNoPositionalArguments do
+ defmacro __using__(_) do
+ quote do
+ def validate(args, _) when length(args) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+
+ # Note: this will accept everything, so it must be the
+ # last validation clause defined!
+ def validate(_, _), do: :ok
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positional_argument.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positional_argument.ex
new file mode 100644
index 0000000000..3731775ed3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positional_argument.ex
@@ -0,0 +1,25 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.AcceptsOnePositionalArgument do
+ defmacro __using__(_) do
+ quote do
+ def validate(args, _) when length(args) == 0 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ # Note: this will accept everything, so it must be the
+ # last validation clause defined!
+ def validate(_, _), do: :ok
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positive_integer_argument.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positive_integer_argument.ex
new file mode 100644
index 0000000000..6b6fd28dfe
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_one_positive_integer_argument.ex
@@ -0,0 +1,34 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.AcceptsOnePositiveIntegerArgument do
+ defmacro __using__(_) do
+ quote do
+ def validate(args, _) when length(args) == 0 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([value], _) when is_integer(value) do
+ :ok
+ end
+
+ def validate([value], _) do
+ case Integer.parse(value) do
+ {n, _} when n >= 1 -> :ok
+ :error -> {:validation_failure, {:bad_argument, "Argument must be a positive integer"}}
+ end
+ end
+
+ def validate(_, _), do: :ok
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_two_positional_arguments.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_two_positional_arguments.ex
new file mode 100644
index 0000000000..18b2740b42
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/accepts_two_positional_arguments.ex
@@ -0,0 +1,25 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments do
+ defmacro __using__(_) do
+ quote do
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ # Note: this will accept everything, so it must be the
+ # last validation clause defined!
+ def validate([_, _], _), do: :ok
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/alarms.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/alarms.ex
new file mode 100644
index 0000000000..15143f7f69
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/alarms.ex
@@ -0,0 +1,88 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Alarms do
+ def alarm_lines(alarms, node_name) do
+ Enum.reduce(alarms, [], fn
+ :file_descriptor_limit, acc ->
+ ["File descriptor limit alarm on node #{node_name}" | acc]
+
+ {{:resource_limit, :memory, alarmed_node_name}, _}, acc ->
+ ["Memory alarm on node #{alarmed_node_name}" | acc]
+
+ {:resource_limit, :memory, alarmed_node_name}, acc ->
+ ["Memory alarm on node #{alarmed_node_name}" | acc]
+
+ {{:resource_limit, :disk, alarmed_node_name}, _}, acc ->
+ ["Free disk space alarm on node #{alarmed_node_name}" | acc]
+
+ {:resource_limit, :disk, alarmed_node_name}, acc ->
+ ["Free disk space alarm on node #{alarmed_node_name}" | acc]
+ end)
+ |> Enum.reverse()
+ end
+
+ def local_alarms(alarms, node_name) do
+ Enum.filter(
+ alarms,
+ fn
+ # local by definition
+ :file_descriptor_limit ->
+ true
+
+ {{:resource_limit, _, a_node}, _} ->
+ node_name == a_node
+ end
+ )
+ end
+
+ def clusterwide_alarms(alarms, node_name) do
+ alarms
+ |> Enum.reject(fn x -> x == :file_descriptor_limit end)
+ |> Enum.filter(fn {{:resource_limit, _, a_node}, _} ->
+ a_node != node_name
+ end)
+ end
+
+ def alarm_types(xs) do
+ Enum.map(xs, &alarm_type/1)
+ end
+
+ def alarm_type(val) when is_atom(val) do
+ val
+ end
+ def alarm_type({:resource_limit, val, _node}) do
+ val
+ end
+ def alarm_type({{:resource_limit, val, _node}, []}) do
+ val
+ end
+
+ def alarm_maps(xs) do
+ Enum.map(xs, &alarm_map/1)
+ end
+ def alarm_map(:file_descriptor_limit) do
+ %{
+ type: :resource_limit,
+ resource: :file_descriptors,
+ node: node()
+ }
+ end
+ def alarm_map({{:resource_limit, resource, node}, _}) do
+ %{
+ type: :resource_limit,
+ resource: resource,
+ node: node
+ }
+ end
+ def alarm_map({:resource_limit, resource, node}) do
+ %{
+ type: :resource_limit,
+ resource: resource,
+ node: node
+ }
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/ansi.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/ansi.ex
new file mode 100644
index 0000000000..e541a632ff
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/ansi.ex
@@ -0,0 +1,35 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.ANSI do
+ def bright(string) do
+ "#{IO.ANSI.bright()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def red(string) do
+ "#{IO.ANSI.red()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def yellow(string) do
+ "#{IO.ANSI.yellow()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def magenta(string) do
+ "#{IO.ANSI.magenta()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def bright_red(string) do
+ "#{IO.ANSI.bright()}#{IO.ANSI.red()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def bright_yellow(string) do
+ "#{IO.ANSI.bright()}#{IO.ANSI.yellow()}#{string}#{IO.ANSI.reset()}"
+ end
+
+ def bright_magenta(string) do
+ "#{IO.ANSI.bright()}#{IO.ANSI.magenta()}#{string}#{IO.ANSI.reset()}"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/code_path.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/code_path.ex
new file mode 100644
index 0000000000..32636fac6f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/code_path.ex
@@ -0,0 +1,108 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.CodePath do
+ alias RabbitMQ.CLI.Core.{Config, Paths, Platform}
+
+ def add_plugins_to_load_path(opts) do
+ with {:ok, plugins_dir} <- Paths.plugins_dir(opts) do
+ String.split(to_string(plugins_dir), Platform.path_separator())
+ |> Enum.map(&add_directory_plugins_to_load_path/1)
+
+ :ok
+ end
+ end
+
+ def add_directory_plugins_to_load_path(directory_with_plugins_inside_it) do
+ with {:ok, files} <- File.ls(directory_with_plugins_inside_it) do
+ Enum.map(
+ files,
+ fn filename ->
+ cond do
+ String.ends_with?(filename, [".ez"]) ->
+ Path.join([directory_with_plugins_inside_it, filename])
+ |> String.to_charlist()
+ |> add_archive_code_path()
+
+ File.dir?(filename) ->
+ Path.join([directory_with_plugins_inside_it, filename])
+ |> add_dir_code_path()
+
+ true ->
+ {:error, {:not_a_plugin, filename}}
+ end
+ end
+ )
+ end
+ end
+
+ defp add_archive_code_path(ez_dir) do
+ case :erl_prim_loader.list_dir(ez_dir) do
+ {:ok, [app_dir]} ->
+ app_in_ez = :filename.join(ez_dir, app_dir)
+ add_dir_code_path(app_in_ez)
+
+ _ ->
+ {:error, :no_app_dir}
+ end
+ end
+
+ defp add_dir_code_path(app_dir_0) do
+ app_dir = to_charlist(app_dir_0)
+
+ case :erl_prim_loader.list_dir(app_dir) do
+ {:ok, list} ->
+ case Enum.member?(list, 'ebin') do
+ true ->
+ ebin_dir = :filename.join(app_dir, 'ebin')
+ Code.append_path(ebin_dir)
+
+ false ->
+ {:error, :no_ebin}
+ end
+
+ _ ->
+ {:error, :app_dir_empty}
+ end
+ end
+
+ def require_rabbit_and_plugins(_, opts) do
+ require_rabbit_and_plugins(opts)
+ end
+
+ def require_rabbit_and_plugins(opts) do
+ with :ok <- require_rabbit(opts),
+ :ok <- add_plugins_to_load_path(opts),
+ do: :ok
+ end
+
+ def require_rabbit(_, opts) do
+ require_rabbit(opts)
+ end
+
+ def require_rabbit(opts) do
+ home = Config.get_option(:rabbitmq_home, opts)
+
+ case home do
+ nil ->
+ {:error, {:unable_to_load_rabbit, :rabbitmq_home_is_undefined}}
+
+ _ ->
+ case Application.load(:rabbit) do
+ :ok ->
+ Code.ensure_loaded(:rabbit_plugins)
+ :ok
+
+ {:error, {:already_loaded, :rabbit}} ->
+ Code.ensure_loaded(:rabbit_plugins)
+ :ok
+
+ {:error, err} ->
+ {:error, {:unable_to_load_rabbit, err}}
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/command_modules.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/command_modules.ex
new file mode 100644
index 0000000000..a1b7fc9237
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/command_modules.ex
@@ -0,0 +1,196 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.CommandModules do
+ alias RabbitMQ.CLI.Core.Config
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginsHelpers
+ alias RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.CodePath
+
+ @commands_ns ~r/RabbitMQ.CLI.(.*).Commands/
+
+ def module_map(opts \\ %{}) do
+ Application.get_env(:rabbitmqctl, :commands) || load(opts)
+ end
+
+ def module_map_core(opts \\ %{}) do
+ Application.get_env(:rabbitmqctl, :commands_core) || load_core(opts)
+ end
+
+ def load_core(opts) do
+ scope = script_scope(opts)
+ commands = load_commands_core(scope)
+ Application.put_env(:rabbitmqctl, :commands_core, commands)
+ commands
+ end
+
+ def load(opts) do
+ scope = script_scope(opts)
+ commands = load_commands(scope, opts)
+ Application.put_env(:rabbitmqctl, :commands, commands)
+ commands
+ end
+
+ def script_scope(opts) do
+ scopes = Application.get_env(:rabbitmqctl, :scopes, [])
+ scopes[Config.get_option(:script_name, opts)] || :none
+ end
+
+ def load_commands_core(scope) do
+ make_module_map(ctl_modules(), scope)
+ end
+
+ def load_commands(scope, opts) do
+ make_module_map(plugin_modules(opts) ++ ctl_modules(), scope)
+ end
+
+ def ctl_modules() do
+ Application.spec(:rabbitmqctl, :modules)
+ end
+
+ def plugin_modules(opts) do
+ require_rabbit(opts)
+
+ enabled_plugins =
+ try do
+ PluginsHelpers.read_enabled(opts)
+ catch
+ err ->
+ {:ok, enabled_plugins_file} = PluginsHelpers.enabled_plugins_file(opts)
+ require Logger
+
+ Logger.warn(
+ "Unable to read the enabled plugins file.\n" <>
+ " Reason: #{inspect(err)}\n" <>
+ " Commands provided by plugins will not be available.\n" <>
+ " Please make sure your user has sufficient permissions to read to\n" <>
+ " #{enabled_plugins_file}"
+ )
+
+ []
+ end
+
+ partitioned =
+ Enum.group_by(enabled_plugins, fn app ->
+ case Application.load(app) do
+ :ok -> :loaded
+ {:error, {:already_loaded, ^app}} -> :loaded
+ _ -> :not_found
+ end
+ end)
+
+ loaded = partitioned[:loaded] || []
+ missing = partitioned[:not_found] || []
+ ## If plugins are not in ERL_LIBS, they should be loaded from plugins_dir
+ case missing do
+ [] ->
+ :ok
+
+ _ ->
+ add_plugins_to_load_path(opts)
+ Enum.each(missing, fn app -> Application.load(app) end)
+ end
+
+ Enum.flat_map(loaded ++ missing, fn app ->
+ Application.spec(app, :modules) || []
+ end)
+ end
+
+ defp make_module_map(modules, scope) do
+ commands_ns = Regex.recompile!(@commands_ns)
+
+ modules
+ |> Enum.filter(fn mod ->
+ to_string(mod) =~ commands_ns and
+ module_exists?(mod) and
+ implements_command_behaviour?(mod) and
+ command_in_scope(mod, scope)
+ end)
+ |> Enum.map(&command_tuple/1)
+ |> Map.new()
+ end
+
+ defp module_exists?(nil) do
+ false
+ end
+
+ defp module_exists?(mod) do
+ Code.ensure_loaded?(mod)
+ end
+
+ defp implements_command_behaviour?(nil) do
+ false
+ end
+
+ defp implements_command_behaviour?(module) do
+ Enum.member?(
+ module.module_info(:attributes)[:behaviour] || [],
+ RabbitMQ.CLI.CommandBehaviour
+ )
+ end
+
+ def module_to_command(mod) do
+ mod
+ |> to_string
+ |> strip_namespace
+ |> to_snake_case
+ |> String.replace_suffix("_command", "")
+ end
+
+ defp command_tuple(cmd) do
+ {module_to_command(cmd), cmd}
+ end
+
+ def strip_namespace(str) do
+ str
+ |> String.split(".")
+ |> List.last()
+ end
+
+ def to_snake_case(<<c, str::binary>>) do
+ tail = for <<c <- str>>, into: "", do: snake(c)
+ <<to_lower_char(c), tail::binary>>
+ end
+
+ defp snake(c) do
+ if c >= ?A and c <= ?Z do
+ <<"_", c + 32>>
+ else
+ <<c>>
+ end
+ end
+
+ defp to_lower_char(c) do
+ if c >= ?A and c <= ?Z do
+ c + 32
+ else
+ c
+ end
+ end
+
+ defp command_in_scope(_cmd, :all) do
+ true
+ end
+
+ defp command_in_scope(cmd, scope) do
+ Enum.member?(command_scopes(cmd), scope)
+ end
+
+ defp command_scopes(cmd) do
+ case CommandBehaviour.scopes(cmd) do
+ nil ->
+ Regex.recompile!(@commands_ns)
+ |> Regex.run(to_string(cmd), capture: :all_but_first)
+ |> List.first()
+ |> to_snake_case
+ |> String.to_atom()
+ |> List.wrap()
+ scopes ->
+ scopes
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/config.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/config.ex
new file mode 100644
index 0000000000..251f9e582f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/config.ex
@@ -0,0 +1,200 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Config do
+ alias RabbitMQ.CLI.{
+ CommandBehaviour,
+ FormatterBehaviour,
+ PrinterBehaviour
+ }
+
+ alias RabbitMQ.CLI.Core.Helpers
+
+ #
+ # Environment
+ #
+
+ def get_option(name, opts \\ %{}) do
+ raw_option =
+ opts[name] ||
+ get_system_option(name, opts) ||
+ default(name)
+
+ normalise(name, raw_option)
+ end
+
+ def output_less?(opts) do
+ Map.get(opts, :silent, false) || Map.get(opts, :quiet, false)
+ end
+
+ def normalise(:node, nil), do: nil
+
+ def normalise(:node, node) when not is_atom(node) do
+ RabbitMQ.CLI.Core.DataCoercion.to_atom(node)
+ end
+
+ def normalise(:erlang_cookie, nil), do: nil
+
+ def normalise(:erlang_cookie, c) when not is_atom(c) do
+ RabbitMQ.CLI.Core.DataCoercion.to_atom(c)
+ end
+
+ def normalise(:longnames, true), do: :longnames
+ def normalise(:longnames, "true"), do: :longnames
+ def normalise(:longnames, 'true'), do: :longnames
+ def normalise(:longnames, "\"true\""), do: :longnames
+ def normalise(:longnames, _val), do: :shortnames
+ def normalise(_, value), do: value
+
+ def get_system_option(:script_name, _) do
+ Path.basename(:escript.script_name())
+ |> Path.rootname()
+ |> String.to_atom()
+ end
+
+ def get_system_option(:aliases_file, _) do
+ System.get_env("RABBITMQ_CLI_ALIASES_FILE")
+ end
+
+ def get_system_option(:erlang_cookie, _) do
+ System.get_env("RABBITMQ_ERLANG_COOKIE")
+ end
+
+ def get_system_option(:node, %{offline: true} = opts) do
+ remote_node =
+ case opts[:node] do
+ nil -> nil
+ val -> Helpers.normalise_node_option(val, opts[:longnames], opts)
+ end
+
+ context = get_env_context(remote_node, true)
+ get_val_from_env_context(context, :node)
+ end
+
+ def get_system_option(:node, opts) do
+ remote_node =
+ case opts[:node] do
+ nil -> nil
+ val -> Helpers.normalise_node_option(val, opts[:longnames], opts)
+ end
+
+ context = get_env_context(remote_node, false)
+ get_val_from_env_context(context, :node)
+ end
+
+ def get_system_option(name, opts) do
+ work_offline = opts[:offline] == true
+
+ remote_node =
+ case name do
+ :longnames -> nil
+ :rabbitmq_home -> nil
+ _ -> node_flag_or_default(opts)
+ end
+
+ context = get_env_context(remote_node, work_offline)
+ val0 = get_val_from_env_context(context, name)
+
+ val =
+ cond do
+ remote_node != nil and
+ val0 == :undefined and
+ (name == :mnesia_dir or name == :feature_flags_file or name == :plugins_dir or
+ name == :enabled_plugins_file) ->
+ context1 = get_env_context(nil, true)
+ get_val_from_env_context(context1, name)
+
+ true ->
+ val0
+ end
+
+ case val do
+ :undefined -> nil
+ _ -> val
+ end
+ end
+
+ def get_env_context(nil, _) do
+ :rabbit_env.get_context()
+ end
+
+ def get_env_context(remote_node, work_offline) do
+ case work_offline do
+ true -> :rabbit_env.get_context(:offline)
+ false -> :rabbit_env.get_context(remote_node)
+ end
+ end
+
+ def get_val_from_env_context(context, name) do
+ case name do
+ :node -> context[:nodename]
+ :longnames -> context[:nodename_type] == :longnames
+ :rabbitmq_home -> context[:rabbitmq_home]
+ :mnesia_dir -> context[:mnesia_dir]
+ :plugins_dir -> context[:plugins_path]
+ :plugins_expand_dir -> context[:plugins_expand_dir]
+ :feature_flags_file -> context[:feature_flags_file]
+ :enabled_plugins_file -> context[:enabled_plugins_file]
+ end
+ end
+
+ def node_flag_or_default(opts) do
+ case opts[:node] do
+ nil ->
+ # Just in case `opts` was not normalized yet (to get the
+ # default node), we do it here as well.
+ case Helpers.normalise_node_option(opts) do
+ {:error, _} -> nil
+ {:ok, normalized_opts} -> normalized_opts[:node]
+ end
+
+ node ->
+ node
+ end
+ end
+
+ def default(:script_name), do: :rabbitmqctl
+ def default(:node), do: :rabbit
+ def default(_), do: nil
+
+ #
+ # Formatters and Printers
+ #
+
+ def get_formatter(command, %{formatter: formatter}) do
+ module_name = FormatterBehaviour.module_name(formatter)
+
+ case Code.ensure_loaded(module_name) do
+ {:module, _} -> module_name
+ {:error, :nofile} -> CommandBehaviour.formatter(command, default_formatter())
+ end
+ end
+
+ def get_formatter(command, _) do
+ CommandBehaviour.formatter(command, default_formatter())
+ end
+
+ def get_printer(command, %{printer: printer}) do
+ module_name = PrinterBehaviour.module_name(printer)
+
+ case Code.ensure_loaded(module_name) do
+ {:module, _} -> module_name
+ {:error, :nofile} -> CommandBehaviour.printer(command, default_printer())
+ end
+ end
+
+ def get_printer(command, _) do
+ CommandBehaviour.printer(command, default_printer())
+ end
+
+ def default_formatter() do
+ RabbitMQ.CLI.Formatters.String
+ end
+
+ def default_printer() do
+ RabbitMQ.CLI.Printers.StdIO
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/data_coercion.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/data_coercion.ex
new file mode 100644
index 0000000000..9c3d3e7344
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/data_coercion.ex
@@ -0,0 +1,21 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defprotocol RabbitMQ.CLI.Core.DataCoercion do
+ def to_atom(data)
+end
+
+defimpl RabbitMQ.CLI.Core.DataCoercion, for: Atom do
+ def to_atom(atom), do: atom
+end
+
+defimpl RabbitMQ.CLI.Core.DataCoercion, for: BitString do
+ def to_atom(string), do: String.to_atom(string)
+end
+
+defimpl RabbitMQ.CLI.Core.DataCoercion, for: List do
+ def to_atom(list), do: List.to_atom(list)
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/distribution.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/distribution.ex
new file mode 100644
index 0000000000..403c9dd970
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/distribution.ex
@@ -0,0 +1,138 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Distribution do
+ alias RabbitMQ.CLI.Core.{ANSI, Config, Helpers}
+
+ #
+ # API
+ #
+
+ def start() do
+ start(%{})
+ end
+
+ def start(options) do
+ node_name_type = Config.get_option(:longnames, options)
+ result = start(node_name_type, 10, :undefined)
+ ensure_cookie(options)
+ result
+ end
+
+ def stop, do: Node.stop()
+
+ def start_as(node_name, options) do
+ node_name_type = Config.get_option(:longnames, options)
+ result = start_with_epmd(node_name, node_name_type)
+ ensure_cookie(options)
+ result
+ end
+
+ ## Optimization. We try to start EPMD only if distribution fails
+ def start_with_epmd(node_name, node_name_type) do
+ case Node.start(node_name, node_name_type) do
+ {:ok, _} = ok ->
+ ok
+
+ {:error, {:already_started, _}} = started ->
+ started
+
+ {:error, {{:already_started, _}, _}} = started ->
+ started
+
+ ## EPMD can be stopped. Retry with EPMD
+ {:error, _} ->
+ :rabbit_nodes_common.ensure_epmd()
+ Node.start(node_name, node_name_type)
+ end
+ end
+
+ def per_node_timeout(:infinity, _) do
+ :infinity
+ end
+
+ def per_node_timeout(timeout, node_count) do
+ Kernel.trunc(timeout / node_count)
+ end
+
+ #
+ # Implementation
+ #
+
+ def ensure_cookie(options) do
+ case Config.get_option(:erlang_cookie, options) do
+ nil ->
+ :ok
+
+ cookie ->
+ Node.set_cookie(cookie)
+ maybe_warn_about_deprecated_rabbitmq_erlang_cookie_env_variable(options)
+ :ok
+ end
+ end
+
+ defp start(_opt, 0, last_err) do
+ {:error, last_err}
+ end
+
+ defp start(node_name_type, attempts, _last_err) do
+ candidate = generate_cli_node_name(node_name_type)
+
+ case start_with_epmd(candidate, node_name_type) do
+ {:ok, _} ->
+ :ok
+
+ {:error, {:already_started, pid}} ->
+ {:ok, pid}
+
+ {:error, {{:already_started, pid}, _}} ->
+ {:ok, pid}
+
+ {:error, reason} ->
+ start(node_name_type, attempts - 1, reason)
+ end
+ end
+
+ defp generate_cli_node_name(node_name_type) do
+ case Helpers.get_rabbit_hostname(node_name_type) do
+ {:error, _} = err ->
+ throw(err)
+
+ rmq_hostname ->
+ # This limits the number of possible unique node names used by CLI tools to avoid
+ # the atom table from growing above the node limit. We must use reasonably unique IDs
+ # to allow for concurrent CLI tool execution.
+ #
+ # Enum.random/1 is constant time and space with range arguments https://hexdocs.pm/elixir/Enum.html#random/1.
+ id = Enum.random(1..1024)
+ String.to_atom("rabbitmqcli-#{id}-#{rmq_hostname}")
+ end
+ end
+
+ defp maybe_warn_about_deprecated_rabbitmq_erlang_cookie_env_variable(options) do
+ case System.get_env("RABBITMQ_ERLANG_COOKIE") do
+ nil ->
+ :ok
+
+ _ ->
+ case Config.output_less?(options) do
+ true ->
+ :ok
+
+ false ->
+ warning =
+ ANSI.bright_red(
+ "RABBITMQ_ERLANG_COOKIE env variable support is deprecated and will be REMOVED in a future version. "
+ ) <>
+ ANSI.yellow(
+ "Use the $HOME/.erlang.cookie file or the --erlang-cookie switch instead."
+ )
+
+ IO.puts(warning)
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/doc_guide.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/doc_guide.ex
new file mode 100644
index 0000000000..c75dcb0d7c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/doc_guide.ex
@@ -0,0 +1,67 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.DocGuide.Macros do
+ @moduledoc """
+ Helper module that works around a compiler limitation: macros cannot
+ be used in a module that defines them.
+ """
+ @default_domain "rabbitmq.com"
+
+ defmacro defguide(name, opts \\ []) do
+ domain = Keyword.get(opts, :domain, @default_domain)
+ fn_name = String.to_atom(name)
+ path_segment = Keyword.get(opts, :path_segment, String.replace(name, "_", "-"))
+
+ quote do
+ def unquote(fn_name)() do
+ unquote("https://#{domain}/#{path_segment}.html")
+ end
+ end
+ end
+end
+
+defmodule RabbitMQ.CLI.Core.DocGuide do
+ require RabbitMQ.CLI.Core.DocGuide.Macros
+ alias RabbitMQ.CLI.Core.DocGuide.Macros
+
+ #
+ # API
+ #
+
+ Macros.defguide("access_control")
+ Macros.defguide("alarms")
+ Macros.defguide("disk_alarms")
+ Macros.defguide("alternate_exchange", path_segment: "ae")
+ Macros.defguide("channels")
+ Macros.defguide("cli")
+ Macros.defguide("clustering")
+ Macros.defguide("cluster_formation")
+ Macros.defguide("connections")
+ Macros.defguide("configuration", path_segment: "configure")
+ Macros.defguide("consumers")
+ Macros.defguide("definitions")
+ Macros.defguide("erlang_versions", path_segment: "which-erlang")
+ Macros.defguide("feature_flags")
+ Macros.defguide("firehose")
+ Macros.defguide("mirroring", path_segment: "ha")
+ Macros.defguide("logging")
+ Macros.defguide("management")
+ Macros.defguide("memory_use")
+ Macros.defguide("monitoring")
+ Macros.defguide("networking")
+ Macros.defguide("parameters")
+ Macros.defguide("publishers")
+ Macros.defguide("plugins")
+ Macros.defguide("queues")
+ Macros.defguide("quorum_queues", domain: "next.rabbitmq.com")
+ Macros.defguide("stream_queues", domain: "next.rabbitmq.com")
+ Macros.defguide("runtime_tuning", path_segment: "runtime")
+ Macros.defguide("tls", path_segment: "ssl")
+ Macros.defguide("troubleshooting")
+ Macros.defguide("virtual_hosts", path_segments: "vhosts")
+ Macros.defguide("upgrade")
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/erl_eval.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/erl_eval.ex
new file mode 100644
index 0000000000..8d2e3aff9e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/erl_eval.ex
@@ -0,0 +1,26 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.ErlEval do
+ def parse_expr(expr) do
+ expr_str = to_charlist(expr)
+
+ case :erl_scan.string(expr_str) do
+ {:ok, scanned, _} ->
+ case :erl_parse.parse_exprs(scanned) do
+ {:ok, parsed} -> {:ok, parsed}
+ {:error, err} -> {:error, format_parse_error(err)}
+ end
+
+ {:error, err, _} ->
+ {:error, format_parse_error(err)}
+ end
+ end
+
+ defp format_parse_error({_line, mod, err}) do
+ to_string(:lists.flatten(mod.format_error(err)))
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/exit_codes.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/exit_codes.ex
new file mode 100644
index 0000000000..9e416d7153
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/exit_codes.ex
@@ -0,0 +1,56 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Lists predefined error exit codes used by RabbitMQ CLI tools.
+# The codes are adopted from [1], which (according to our team's research)
+# is possibly the most standardized set of command line tool exit codes there is.
+#
+# 1. https://www.freebsd.org/cgi/man.cgi?query=sysexits&apropos=0&sektion=0&manpath=FreeBSD+12.0-RELEASE&arch=default&format=html
+defmodule RabbitMQ.CLI.Core.ExitCodes do
+ @exit_ok 0
+ @exit_usage 64
+ @exit_dataerr 65
+ @exit_nouser 67
+ @exit_unavailable 69
+ @exit_software 70
+ @exit_tempfail 75
+ @exit_config 78
+
+ @type exit_code :: integer
+
+ def exit_ok, do: @exit_ok
+ def exit_usage, do: @exit_usage
+ def exit_dataerr, do: @exit_dataerr
+ def exit_nouser, do: @exit_nouser
+ def exit_unavailable, do: @exit_unavailable
+ def exit_software, do: @exit_software
+ def exit_tempfail, do: @exit_tempfail
+ def exit_config, do: @exit_config
+
+ def exit_code_for({:validation_failure, :not_enough_args}), do: exit_usage()
+ def exit_code_for({:validation_failure, :too_many_args}), do: exit_usage()
+ def exit_code_for({:validation_failure, {:not_enough_args, _}}), do: exit_usage()
+ def exit_code_for({:validation_failure, {:too_many_args, _}}), do: exit_usage()
+ def exit_code_for({:validation_failure, {:bad_argument, _}}), do: exit_dataerr()
+ def exit_code_for({:validation_failure, :bad_argument}), do: exit_dataerr()
+ def exit_code_for({:validation_failure, :eperm}), do: exit_dataerr()
+ def exit_code_for({:validation_failure, {:bad_option, _}}), do: exit_usage()
+ def exit_code_for({:validation_failure, _}), do: exit_usage()
+ # a special case of bad_argument
+ def exit_code_for({:no_such_vhost, _}), do: exit_dataerr()
+ def exit_code_for({:no_such_user, _}), do: exit_nouser()
+ def exit_code_for({:badrpc_multi, :timeout, _}), do: exit_tempfail()
+ def exit_code_for({:badrpc, :timeout}), do: exit_tempfail()
+ def exit_code_for({:badrpc, {:timeout, _}}), do: exit_tempfail()
+ def exit_code_for({:badrpc, {:timeout, _, _}}), do: exit_tempfail()
+ def exit_code_for(:timeout), do: exit_tempfail()
+ def exit_code_for({:timeout, _}), do: exit_tempfail()
+ def exit_code_for({:badrpc_multi, :nodedown, _}), do: exit_unavailable()
+ def exit_code_for({:badrpc, :nodedown}), do: exit_unavailable()
+ def exit_code_for({:node_name, _}), do: exit_dataerr()
+ def exit_code_for({:incompatible_version, _, _}), do: exit_unavailable()
+ def exit_code_for({:error, _}), do: exit_unavailable()
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/feature_flags.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/feature_flags.ex
new file mode 100644
index 0000000000..4b4b8c8d5d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/feature_flags.ex
@@ -0,0 +1,19 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.FeatureFlags do
+
+ #
+ # API
+ #
+
+ def feature_flag_lines(feature_flags) do
+ feature_flags
+ |> Enum.map(fn %{name: name, state: state} ->
+ "Flag: #{name}, state: #{state}"
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/helpers.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/helpers.ex
new file mode 100644
index 0000000000..97bd7c7bd9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/helpers.ex
@@ -0,0 +1,148 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Helpers do
+ alias RabbitMQ.CLI.Core.{Config, NodeName}
+ require Record
+
+ def get_rabbit_hostname(node_name_type \\ :shortnames) do
+ normalise_node(Config.get_option(:node), node_name_type)
+ end
+
+ def normalise_node(nil, node_name_type) do
+ normalise_node(Config.get_option(:node), node_name_type)
+ end
+
+ def normalise_node(name, node_name_type) do
+ case NodeName.create(name, node_name_type) do
+ {:ok, node_name} -> node_name
+ other -> other
+ end
+ end
+
+ # rabbitmq/rabbitmq-cli#278
+ def normalise_node_option(options) do
+ node_opt = Config.get_option(:node, options)
+ longnames_opt = Config.get_option(:longnames, options)
+ case NodeName.create(node_opt, longnames_opt) do
+ {:error, _} = err ->
+ err
+ {:ok, val} ->
+ {:ok, Map.put(options, :node, val)}
+ end
+ end
+
+ def normalise_node_option(nil, _, _) do
+ nil
+ end
+ def normalise_node_option(node_opt, longnames_opt, options) do
+ case NodeName.create(node_opt, longnames_opt) do
+ {:error, _} = err ->
+ err
+ {:ok, val} ->
+ {:ok, Map.put(options, :node, val)}
+ end
+ end
+
+ def case_insensitive_format(%{format: format} = opts) do
+ %{opts | format: String.downcase(format)}
+ end
+ def case_insensitive_format(opts), do: opts
+
+ def nodes_in_cluster(node, timeout \\ :infinity) do
+ with_nodes_in_cluster(node, fn nodes -> nodes end, timeout)
+ end
+
+ def with_nodes_in_cluster(node, fun, timeout \\ :infinity) do
+ case :rpc.call(node, :rabbit_mnesia, :cluster_nodes, [:running], timeout) do
+ {:badrpc, _} = err -> err
+ value -> fun.(value)
+ end
+ end
+
+ def node_running?(node) do
+ :net_adm.ping(node) == :pong
+ end
+
+ # Convert function to stream
+ def defer(fun) do
+ Stream.iterate(:ok, fn _ -> fun.() end)
+ |> Stream.drop(1)
+ |> Stream.take(1)
+ end
+
+ # Streamify a function sequence passing result
+ # Execution can be terminated by an error {:error, _}.
+ # The error will be the last element in the stream.
+ # Functions can return {:ok, val}, so val will be passed
+ # to then next function, or {:ok, val, output} where
+ # val will be passed and output will be put into the stream
+ def stream_until_error_parameterised(funs, init) do
+ Stream.transform(funs, {:just, init}, fn
+ f, {:just, val} ->
+ case f.(val) do
+ {:error, _} = err -> {[err], :nothing}
+ :ok -> {[], {:just, val}}
+ {:ok, new_val} -> {[], {:just, new_val}}
+ {:ok, new_val, out} -> {[out], {:just, new_val}}
+ end
+
+ _, :nothing ->
+ {:halt, :nothing}
+ end)
+ end
+
+ # Streamify function sequence.
+ # Execution can be terminated by an error {:error, _}.
+ # The error will be the last element in the stream.
+ def stream_until_error(funs) do
+ stream_until_error_parameterised(
+ Enum.map(
+ funs,
+ fn fun ->
+ fn :no_param ->
+ case fun.() do
+ {:error, _} = err -> err
+ other -> {:ok, :no_param, other}
+ end
+ end
+ end
+ ),
+ :no_param
+ )
+ end
+
+ def apply_if_exported(mod, fun, args, default) do
+ Code.ensure_loaded(mod)
+ case function_exported?(mod, fun, length(args)) do
+ true -> apply(mod, fun, args)
+ false -> default
+ end
+ end
+
+ def cli_acting_user, do: "rmq-cli"
+
+ def string_or_inspect(val) do
+ case String.Chars.impl_for(val) do
+ nil ->
+ inspect(val)
+
+ _ ->
+ try do
+ to_string(val)
+ catch
+ _, _ -> inspect(val)
+ end
+ end
+ end
+
+ def evaluate_input_as_term(input) do
+ {:ok, tokens, _end_line} = :erl_scan.string(to_charlist(input <> "."))
+ {:ok, abs_form} = :erl_parse.parse_exprs(tokens)
+ {:value, term_value, _bs} = :erl_eval.exprs(abs_form, :erl_eval.new_bindings())
+ term_value
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/input.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/input.ex
new file mode 100644
index 0000000000..5e1328be29
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/input.ex
@@ -0,0 +1,39 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Input do
+ alias RabbitMQ.CLI.Core.Config
+
+ def consume_single_line_string_with_prompt(prompt, opts) do
+ val = case Config.output_less?(opts) do
+ true ->
+ IO.read(:stdio, :line)
+ false ->
+ IO.puts(prompt)
+ IO.read(:stdio, :line)
+ end
+
+ case val do
+ :eof -> :eof
+ "" -> :eof
+ s -> String.trim(s)
+ end
+ end
+
+ def consume_multiline_string() do
+ val = IO.read(:stdio, :all)
+
+ case val do
+ :eof -> :eof
+ "" -> :eof
+ s -> String.trim(s)
+ end
+ end
+
+ def infer_password(prompt, opts) do
+ consume_single_line_string_with_prompt(prompt, opts)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/listeners.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/listeners.ex
new file mode 100644
index 0000000000..0bc162186c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/listeners.ex
@@ -0,0 +1,312 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Listeners do
+ import Record, only: [defrecord: 3, extract: 2]
+ import RabbitCommon.Records
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ #
+ # API
+ #
+
+ defrecord :certificate, :Certificate, extract(:Certificate, from_lib: "public_key/include/public_key.hrl")
+ defrecord :tbscertificate, :TBSCertificate, extract(:TBSCertificate, from_lib: "public_key/include/public_key.hrl")
+ defrecord :validity, :Validity, extract(:Validity, from_lib: "public_key/include/public_key.hrl")
+
+ def listeners_on(listeners, target_node) do
+ Enum.filter(listeners, fn listener(node: node) ->
+ node == target_node
+ end)
+ end
+
+ def listeners_with_certificates(listeners) do
+ Enum.filter(listeners, fn listener(opts: opts) ->
+ Keyword.has_key?(opts, :cacertfile) or Keyword.has_key?(opts, :certfile)
+ end)
+ end
+
+ def listener_lines(listeners) do
+ listeners
+ |> listener_maps
+ |> Enum.map(fn %{interface: interface, port: port, protocol: protocol} ->
+ "Interface: #{interface}, port: #{port}, protocol: #{protocol}, purpose: #{
+ protocol_label(to_atom(protocol))
+ }"
+ end)
+ end
+ def listener_lines(listeners, node) do
+ listeners
+ |> listener_maps
+ |> Enum.map(fn %{interface: interface, port: port, protocol: protocol} ->
+ "Node: #{node}, interface: #{interface}, port: #{port}, protocol: #{protocol}, purpose: #{
+ protocol_label(to_atom(protocol))
+ }"
+ end)
+ end
+
+ def listener_map(listener) when is_map(listener) do
+ listener
+ end
+ def listener_map(listener) do
+ # Listener options are left out intentionally: they can contain deeply nested values
+ # that are impossible to serialise to JSON.
+ #
+ # Management plugin/HTTP API had its fair share of bugs because of that
+ # and now filters out a lot of options. Raw listener data can be seen in
+ # rabbitmq-diagnostics status.
+ listener(node: node, protocol: protocol, ip_address: interface, port: port) = listener
+
+ %{
+ node: node,
+ protocol: protocol,
+ interface: :inet.ntoa(interface) |> to_string |> maybe_enquote_interface,
+ port: port,
+ purpose: protocol_label(to_atom(protocol))
+ }
+ end
+
+ def listener_maps(listeners) do
+ Enum.map(listeners, &listener_map/1)
+ end
+
+ def listener_certs(listener) do
+ listener(node: node, protocol: protocol, ip_address: interface, port: port, opts: opts) = listener
+
+ %{
+ node: node,
+ protocol: protocol,
+ interface: :inet.ntoa(interface) |> to_string |> maybe_enquote_interface,
+ port: port,
+ purpose: protocol_label(to_atom(protocol)),
+ certfile: read_cert(Keyword.get(opts, :certfile)),
+ cacertfile: read_cert(Keyword.get(opts, :cacertfile))
+ }
+ end
+
+ def read_cert(nil) do
+ nil
+ end
+ def read_cert({:pem, pem}) do
+ pem
+ end
+ def read_cert(path) do
+ case File.read(path) do
+ {:ok, bin} ->
+ bin
+ {:error, _} = err ->
+ err
+ end
+ end
+
+ def listener_expiring_within(listener, seconds) do
+ listener(node: node, protocol: protocol, ip_address: interface, port: port, opts: opts) = listener
+ certfile = Keyword.get(opts, :certfile)
+ cacertfile = Keyword.get(opts, :cacertfile)
+ now = :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())
+ expiry_date = now + seconds
+ certfile_expires_on = expired(cert_validity(read_cert(certfile)), expiry_date)
+ cacertfile_expires_on = expired(cert_validity(read_cert(cacertfile)), expiry_date)
+ case {certfile_expires_on, cacertfile_expires_on} do
+ {[], []} ->
+ false
+ _ ->
+ %{
+ node: node,
+ protocol: protocol,
+ interface: interface,
+ port: port,
+ certfile: certfile,
+ cacertfile: cacertfile,
+ certfile_expires_on: certfile_expires_on,
+ cacertfile_expires_on: cacertfile_expires_on
+ }
+ end
+ end
+
+ def expired_listener_map(%{node: node, protocol: protocol, interface: interface, port: port, certfile_expires_on: certfile_expires_on, cacertfile_expires_on: cacertfile_expires_on, certfile: certfile, cacertfile: cacertfile}) do
+ %{
+ node: node,
+ protocol: protocol,
+ interface: :inet.ntoa(interface) |> to_string |> maybe_enquote_interface,
+ port: port,
+ purpose: protocol_label(to_atom(protocol)),
+ certfile: certfile |> to_string,
+ cacertfile: cacertfile |> to_string,
+ certfile_expires_on: expires_on_list(certfile_expires_on),
+ cacertfile_expires_on: expires_on_list(cacertfile_expires_on)
+ }
+ end
+
+ def expires_on_list({:error, _} = error) do
+ [error]
+ end
+ def expires_on_list(expires) do
+ Enum.map(expires, &expires_on/1)
+ end
+
+ def expires_on({:error, _} = error) do
+ error
+ end
+ def expires_on(seconds) do
+ {:ok, naive} = NaiveDateTime.from_erl(:calendar.gregorian_seconds_to_datetime(seconds))
+ NaiveDateTime.to_string(naive)
+ end
+
+ def expired(nil, _) do
+ []
+ end
+ def expired({:error, _} = error, _) do
+ error
+ end
+ def expired(expires, expiry_date) do
+ Enum.filter(expires, fn ({:error, _}) -> true
+ (seconds) -> seconds < expiry_date end)
+ end
+
+ def cert_validity(nil) do
+ nil
+ end
+ def cert_validity(cert) do
+ dsa_entries = :public_key.pem_decode(cert)
+ case dsa_entries do
+ [] ->
+ {:error, "The certificate file provided does not contain any PEM entry."}
+ _ ->
+ now = :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())
+ Enum.map(dsa_entries, fn ({:Certificate, _, _} = dsa_entry) ->
+ certificate(tbsCertificate: tbs_certificate) = :public_key.pem_entry_decode(dsa_entry)
+ tbscertificate(validity: validity) = tbs_certificate
+ validity(notAfter: not_after, notBefore: not_before) = validity
+ start = :pubkey_cert.time_str_2_gregorian_sec(not_before)
+ case start > now do
+ true ->
+ {:ok, naive} = NaiveDateTime.from_erl(:calendar.gregorian_seconds_to_datetime(start))
+ startdate = NaiveDateTime.to_string(naive)
+ {:error, "Certificate is not yet valid. It starts on #{startdate}"}
+ false ->
+ :pubkey_cert.time_str_2_gregorian_sec(not_after)
+ end
+ ({type, _, _}) ->
+ {:error, "The certificate file provided contains a #{type} entry."}
+ end)
+ end
+ end
+
+ def listener_rows(listeners) do
+ for listener(node: node, protocol: protocol, ip_address: interface, port: port) <- listeners do
+ # Listener options are left out intentionally, see above
+ [
+ node: node,
+ protocol: protocol,
+ interface: :inet.ntoa(interface) |> to_string |> maybe_enquote_interface,
+ port: port,
+ purpose: protocol_label(to_atom(protocol))
+ ]
+ end
+ end
+
+ def protocol_label(:amqp), do: "AMQP 0-9-1 and AMQP 1.0"
+ def protocol_label(:'amqp/ssl'), do: "AMQP 0-9-1 and AMQP 1.0 over TLS"
+ def protocol_label(:mqtt), do: "MQTT"
+ def protocol_label(:'mqtt/ssl'), do: "MQTT over TLS"
+ def protocol_label(:stomp), do: "STOMP"
+ def protocol_label(:'stomp/ssl'), do: "STOMP over TLS"
+ def protocol_label(:http), do: "HTTP API"
+ def protocol_label(:https), do: "HTTP API over TLS (HTTPS)"
+ def protocol_label(:"http/web-mqtt"), do: "MQTT over WebSockets"
+ def protocol_label(:"https/web-mqtt"), do: "MQTT over WebSockets and TLS (HTTPS)"
+ def protocol_label(:"http/web-stomp"), do: "STOMP over WebSockets"
+ def protocol_label(:"https/web-stomp"), do: "STOMP over WebSockets and TLS (HTTPS)"
+ def protocol_label(:"http/prometheus"), do: "Prometheus exporter API over HTTP"
+ def protocol_label(:"https/prometheus"), do: "Prometheus exporter API over TLS (HTTPS)"
+ def protocol_label(:clustering), do: "inter-node and CLI tool communication"
+ def protocol_label(other), do: to_string(other)
+
+ def normalize_protocol(proto) do
+ val = proto |> to_string |> String.downcase()
+
+ case val do
+ "amqp091" -> "amqp"
+ "amqp0.9.1" -> "amqp"
+ "amqp0-9-1" -> "amqp"
+ "amqp0_9_1" -> "amqp"
+ "amqp10" -> "amqp"
+ "amqp1.0" -> "amqp"
+ "amqp1-0" -> "amqp"
+ "amqp1_0" -> "amqp"
+ "amqps" -> "amqp/ssl"
+ "mqtt3.1" -> "mqtt"
+ "mqtt3.1.1" -> "mqtt"
+ "mqtt31" -> "mqtt"
+ "mqtt311" -> "mqtt"
+ "mqtt3_1" -> "mqtt"
+ "mqtt3_1_1" -> "mqtt"
+ "mqtts" -> "mqtt/ssl"
+ "mqtt+tls" -> "mqtt/ssl"
+ "mqtt+ssl" -> "mqtt/ssl"
+ "stomp1.0" -> "stomp"
+ "stomp1.1" -> "stomp"
+ "stomp1.2" -> "stomp"
+ "stomp10" -> "stomp"
+ "stomp11" -> "stomp"
+ "stomp12" -> "stomp"
+ "stomp1_0" -> "stomp"
+ "stomp1_1" -> "stomp"
+ "stomp1_2" -> "stomp"
+ "stomps" -> "stomp/ssl"
+ "stomp+tls" -> "stomp/ssl"
+ "stomp+ssl" -> "stomp/ssl"
+ "https" -> "https"
+ "http1" -> "http"
+ "http1.1" -> "http"
+ "http_api" -> "http"
+ "management" -> "http"
+ "management_ui" -> "http"
+ "ui" -> "http"
+ "cli" -> "clustering"
+ "distribution" -> "clustering"
+ "webmqtt" -> "http/web-mqtt"
+ "web-mqtt" -> "http/web-mqtt"
+ "web_mqtt" -> "http/web-mqtt"
+ "webmqtt/tls" -> "https/web-mqtt"
+ "web-mqtt/tls" -> "https/web-mqtt"
+ "webmqtt/ssl" -> "https/web-mqtt"
+ "web-mqtt/ssl" -> "https/web-mqtt"
+ "webmqtt+tls" -> "https/web-mqtt"
+ "web-mqtt+tls" -> "https/web-mqtt"
+ "webmqtt+ssl" -> "https/web-mqtt"
+ "web-mqtt+ssl" -> "https/web-mqtt"
+ "webstomp" -> "http/web-stomp"
+ "web-stomp" -> "http/web-stomp"
+ "web_stomp" -> "http/web-stomp"
+ "webstomp/tls" -> "https/web-stomp"
+ "web-stomp/tls" -> "https/web-stomp"
+ "webstomp/ssl" -> "https/web-stomp"
+ "web-stomp/ssl" -> "https/web-stomp"
+ "webstomp+tls" -> "https/web-stomp"
+ "web-stomp+tls" -> "https/web-stomp"
+ "webstomp+ssl" -> "https/web-stomp"
+ "web-stomp+ssl" -> "https/web-stomp"
+ _ -> val
+ end
+ end
+
+ #
+ # Implementation
+ #
+
+ defp maybe_enquote_interface(value) do
+ # This simplistic way of distinguishing IPv6 addresses,
+ # networks address ranges, etc actually works better
+ # for the kind of values we can get here than :inet functions. MK.
+ regex = Regex.recompile!(~r/:/)
+ case value =~ regex do
+ true -> "[#{value}]"
+ false -> value
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/log_files.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/log_files.ex
new file mode 100644
index 0000000000..b6d104bff0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/log_files.ex
@@ -0,0 +1,52 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.LogFiles do
+ @spec get_log_locations(atom, integer | :infinity) :: [String.t] | {:badrpc, term}
+ def get_log_locations(node_name, timeout) do
+ case :rabbit_misc.rpc_call(node_name,
+ :rabbit_lager, :log_locations, [],
+ timeout) do
+ {:badrpc, _} = error -> error;
+ list -> Enum.map(list, &to_string/1)
+ end
+ end
+
+ @spec get_default_log_location(atom, integer | :infinity) ::
+ {:ok, String.t} | {:badrpc, term} | {:error, term}
+ def get_default_log_location(node_name, timeout) do
+ case get_log_locations(node_name, timeout) do
+ {:badrpc, _} = error -> error;
+ [] -> {:error, "No log files configured on the node"};
+ [first_log | _] = log_locations ->
+ case get_log_config_file_location(node_name, timeout) do
+ {:badrpc, _} = error -> error;
+ nil -> {:ok, first_log};
+ location ->
+ case Enum.member?(log_locations, location) do
+ true -> {:ok, to_string(location)};
+ ## Configured location was not propagated to lager?
+ false -> {:ok, first_log}
+ end
+ end
+ end
+ end
+
+ defp get_log_config_file_location(node_name, timeout) do
+ case :rabbit_misc.rpc_call(node_name,
+ :application, :get_env, [:rabbit, :log, :none],
+ timeout) do
+ {:badrpc, _} = error -> error;
+ :none -> nil;
+ log_config ->
+ case log_config[:file] do
+ nil -> nil;
+ file_config ->
+ file_config[:file]
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/memory.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/memory.ex
new file mode 100644
index 0000000000..92db5b5502
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/memory.ex
@@ -0,0 +1,105 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Memory do
+ alias RabbitMQ.CLI.InformationUnit, as: IU
+
+ def memory_units do
+ ["k", "kiB", "M", "MiB", "G", "GiB", "kB", "MB", "GB", ""]
+ end
+
+ def memory_unit_absolute(num, unit) when is_number(num) and num < 0,
+ do: {:bad_argument, [num, unit]}
+
+ def memory_unit_absolute(num, "k") when is_number(num), do: power_as_int(num, 2, 10)
+ def memory_unit_absolute(num, "kiB") when is_number(num), do: power_as_int(num, 2, 10)
+ def memory_unit_absolute(num, "M") when is_number(num), do: power_as_int(num, 2, 20)
+ def memory_unit_absolute(num, "MiB") when is_number(num), do: power_as_int(num, 2, 20)
+ def memory_unit_absolute(num, "G") when is_number(num), do: power_as_int(num, 2, 30)
+ def memory_unit_absolute(num, "GiB") when is_number(num), do: power_as_int(num, 2, 30)
+ def memory_unit_absolute(num, "kB") when is_number(num), do: power_as_int(num, 10, 3)
+ def memory_unit_absolute(num, "MB") when is_number(num), do: power_as_int(num, 10, 6)
+ def memory_unit_absolute(num, "GB") when is_number(num), do: power_as_int(num, 10, 9)
+ def memory_unit_absolute(num, "") when is_number(num), do: num
+ def memory_unit_absolute(num, unit) when is_number(num), do: {:bad_argument, [unit]}
+ def memory_unit_absolute(num, unit), do: {:bad_argument, [num, unit]}
+
+ def power_as_int(num, x, y), do: round(num * :math.pow(x, y))
+
+ def compute_relative_values(all_pairs) when is_map(all_pairs) do
+ compute_relative_values(Enum.into(all_pairs, []))
+ end
+ def compute_relative_values(all_pairs) do
+ num_pairs = Keyword.delete(all_pairs, :strategy)
+ # Includes RSS, allocated and runtime-used ("erlang") values.
+ # See https://github.com/rabbitmq/rabbitmq-server/pull/1404.
+ totals = Keyword.get(num_pairs, :total)
+ pairs = Keyword.delete(num_pairs, :total)
+ # Should not be necessary but be more defensive.
+ total =
+ max_of(totals) ||
+ Keyword.get(totals, :rss) ||
+ Keyword.get(totals, :allocated) ||
+ Keyword.get(totals, :erlang)
+
+ pairs
+ |> Enum.map(fn {k, v} ->
+ pg = (v / total) |> fraction_to_percent()
+ {k, %{bytes: v, percentage: pg}}
+ end)
+ |> Enum.sort_by(fn {_key, %{bytes: bytes}} -> bytes end, &>=/2)
+ end
+
+ def formatted_watermark(val) when is_float(val) do
+ %{relative: val}
+ end
+ def formatted_watermark({:absolute, val}) do
+ %{absolute: parse_watermark(val)}
+ end
+ def formatted_watermark(val) when is_integer(val) do
+ %{absolute: parse_watermark(val)}
+ end
+ def formatted_watermark(val) when is_bitstring(val) do
+ %{absolute: parse_watermark(val)}
+ end
+ def formatted_watermark(val) when is_list(val) do
+ %{absolute: parse_watermark(val)}
+ end
+
+ def parse_watermark({:absolute, n}) do
+ case IU.parse(n) do
+ {:ok, parsed} -> parsed
+ err -> err
+ end
+ end
+ def parse_watermark(n) when is_bitstring(n) do
+ case IU.parse(n) do
+ {:ok, parsed} -> parsed
+ err -> err
+ end
+ end
+ def parse_watermark(n) when is_list(n) do
+ case IU.parse(n) do
+ {:ok, parsed} -> parsed
+ err -> err
+ end
+ end
+ def parse_watermark(n) when is_float(n) or is_integer(n) do
+ n
+ end
+
+ #
+ # Implementation
+ #
+
+ defp fraction_to_percent(x) do
+ Float.round(x * 100, 2)
+ end
+
+ defp max_of(m) do
+ Keyword.values(m) |> Enum.max()
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_default_virtual_host.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_default_virtual_host.ex
new file mode 100644
index 0000000000..94b1b768b6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_default_virtual_host.ex
@@ -0,0 +1,15 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.MergesDefaultVirtualHost do
+ defmacro __using__(_) do
+ quote do
+ def merge_defaults(args, opts), do: {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_no_defaults.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_no_defaults.ex
new file mode 100644
index 0000000000..0ee6f3f05a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/merges_no_defaults.ex
@@ -0,0 +1,15 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.MergesNoDefaults do
+ defmacro __using__(_) do
+ quote do
+ def merge_defaults(args, opts), do: {args, opts}
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/networking.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/networking.ex
new file mode 100644
index 0000000000..12d99df7c1
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/networking.ex
@@ -0,0 +1,73 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Networking do
+ @type address_family() :: :inet | :inet6
+
+ @spec address_family(String.t() | atom() | charlist() | binary()) :: address_family()
+ def address_family(value) do
+ val = RabbitMQ.CLI.Core.DataCoercion.to_atom(value)
+ case val do
+ :inet -> :inet
+ :inet4 -> :inet
+ :inet6 -> :inet6
+ :ipv4 -> :inet
+ :ipv6 -> :inet6
+ :IPv4 -> :inet
+ :IPv6 -> :inet6
+ end
+ end
+
+ @spec address_family(String.t() | atom()) :: boolean()
+ def valid_address_family?(value) when is_atom(value) do
+ valid_address_family?(to_string(value))
+ end
+ def valid_address_family?("inet"), do: true
+ def valid_address_family?("inet4"), do: true
+ def valid_address_family?("inet6"), do: true
+ def valid_address_family?("ipv4"), do: true
+ def valid_address_family?("ipv6"), do: true
+ def valid_address_family?("IPv4"), do: true
+ def valid_address_family?("IPv6"), do: true
+ def valid_address_family?(_other), do: false
+
+ @spec format_address(:inet.ip_address()) :: String.t()
+ def format_address(addr) do
+ to_string(:inet.ntoa(addr))
+ end
+
+ @spec format_addresses([:inet.ip_address()]) :: [String.t()]
+ def format_addresses(addrs) do
+ Enum.map(addrs, &format_address/1)
+ end
+
+ @spec inetrc_map(nonempty_list()) :: map()
+ def inetrc_map(list) do
+ Enum.reduce(list, %{},
+ fn hosts, acc when is_list(hosts) ->
+ Map.put(acc, "hosts", host_resolution_map(hosts))
+ {k, v}, acc when k == :domain or k == :resolv_conf or k == :hosts_file ->
+ Map.put(acc, to_string(k), to_string(v))
+ {k, v}, acc when is_list(v) when k == :search or k == :lookup ->
+ Map.put(acc, to_string(k), Enum.join(Enum.map(v, &to_string/1), ", "))
+ {k, v}, acc when is_integer(v) ->
+ Map.put(acc, to_string(k), v)
+ {k, v, v2}, acc when is_tuple(v) when k == :nameserver or k == :nameservers or k == :alt_nameserver ->
+ Map.put(acc, to_string(k), "#{:inet.ntoa(v)}:#{v2}")
+ {k, v}, acc when is_tuple(v) when k == :nameserver or k == :nameservers or k == :alt_nameserver ->
+ Map.put(acc, to_string(k), to_string(:inet.ntoa(v)))
+ {k, v}, acc ->
+ Map.put(acc, to_string(k), to_string(v))
+ end)
+ end
+
+ def host_resolution_map(hosts) do
+ Enum.reduce(hosts, %{},
+ fn {:host, address, hosts}, acc ->
+ Map.put(acc, to_string(:inet.ntoa(address)), Enum.map(hosts, &to_string/1))
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex
new file mode 100644
index 0000000000..c39b215ca7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/node_name.ex
@@ -0,0 +1,198 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.NodeName do
+ alias RabbitMQ.CLI.Core.Config
+
+ @moduledoc """
+ Provides functions for correctly constructing node names given a node type and optional base name.
+ """
+
+ @doc """
+ Constructs complete node name based on :longnames or :shortnames and
+ a base name, in the same manner as Erlang/OTP lib/kernel/src/net_kernel.erl
+ """
+ def create(base, type) do
+ create_name(base, type, 1)
+ end
+
+ @doc """
+ Get local hostname
+ """
+ def hostname, do: :inet_db.gethostname() |> List.to_string()
+
+ @doc """
+ Get hostname part of current node name
+ """
+ def hostname_from_node do
+ [_, hostname] = split_node(Node.self())
+ hostname
+ end
+
+ @doc """
+ Get hostname part of given node name
+ """
+ def hostname_from_node(name) do
+ [_, hostname] = split_node(name)
+ hostname
+ end
+
+ def split_node(name) when is_atom(name) do
+ split_node(to_string(name))
+ end
+
+ def split_node(name) do
+ case String.split(name, "@", parts: 2) do
+ ["", host] ->
+ default_name = to_string(Config.default(:node))
+ [default_name, host]
+
+ [_head, _host] = rslt ->
+ rslt
+
+ [head] ->
+ [head, ""]
+ end
+ end
+
+ @doc """
+ Get local domain. If unavailable, makes a good guess. We're using
+ :inet_db here because that's what Erlang/OTP uses when it creates a node
+ name:
+ https://github.com/erlang/otp/blob/8ca061c3006ad69c2a8d1c835d0d678438966dfc/lib/kernel/src/net_kernel.erl#L1363-L1445
+ """
+ def domain do
+ domain(1)
+ end
+
+ #
+ # Implementation
+ #
+
+ defp domain(attempt) do
+ case {attempt, :inet_db.res_option(:domain), :os.type()} do
+ {1, [], _} ->
+ do_load_resolv()
+ domain(0)
+
+ {0, [], {:unix, :darwin}} ->
+ "local"
+
+ {0, [], _} ->
+ "localdomain"
+
+ {_, domain, _} ->
+ List.to_string(domain)
+ end
+ end
+
+ defp create_name(name, long_or_short_names, attempt) do
+ {head, host1} = create_hostpart(name, long_or_short_names)
+
+ case host1 do
+ {:ok, host_part} ->
+ case valid_name_head(head) do
+ true ->
+ {:ok, String.to_atom(head <> "@" <> host_part)}
+
+ false ->
+ {:error, {:node_name, :invalid_node_name_head}}
+ end
+
+ {:error, :long} when attempt == 1 ->
+ do_load_resolv()
+ create_name(name, long_or_short_names, 0)
+
+ {:error, :long} when attempt == 0 ->
+ case valid_name_head(head) do
+ true ->
+ {:ok, String.to_atom(head <> "@" <> hostname() <> "." <> domain())}
+
+ false ->
+ {:error, {:node_name, :invalid_node_name_head}}
+ end
+
+ {:error, :hostname_not_allowed} ->
+ {:error, {:node_name, :hostname_not_allowed}}
+
+ {:error, err_type} ->
+ {:error, {:node_name, err_type}}
+ end
+ end
+
+ defp create_hostpart(name, long_or_short_names) do
+ [head, host] = split_node(name)
+
+ host1 =
+ case {host, long_or_short_names} do
+ {"", :shortnames} ->
+ case :inet_db.gethostname() do
+ inet_db_host when inet_db_host != [] ->
+ {:ok, to_string(inet_db_host)}
+
+ _ ->
+ {:error, :short}
+ end
+
+ {"", :longnames} ->
+ case {:inet_db.gethostname(), :inet_db.res_option(:domain)} do
+ {inet_db_host, inet_db_domain}
+ when inet_db_host != [] and inet_db_domain != [] ->
+ {:ok, to_string(inet_db_host) <> "." <> to_string(inet_db_domain)}
+
+ _ ->
+ {:error, :long}
+ end
+
+ {_, type} ->
+ validate_hostname(host, type)
+ end
+
+ {head, host1}
+ end
+
+ defp validate_hostname(host, :longnames) do
+ case String.contains?(host, ".") do
+ true ->
+ validate_hostname_rx(host)
+
+ _ ->
+ validate_hostname(host <> "." <> domain(), :longnames)
+ end
+ end
+
+ defp validate_hostname(host, :shortnames) do
+ case String.contains?(host, ".") do
+ true ->
+ {:error, :short}
+
+ _ ->
+ validate_hostname_rx(host)
+ end
+ end
+
+ defp validate_hostname_rx(host) do
+ rx = Regex.compile!("^[!-ÿ]*$", [:unicode])
+
+ case Regex.match?(rx, host) do
+ true ->
+ {:ok, host}
+
+ false ->
+ {:error, :hostname_not_allowed}
+ end
+ end
+
+ defp valid_name_head(head) do
+ rx = Regex.compile!("^[0-9A-Za-z_\\-]+$", [:unicode])
+ Regex.match?(rx, head)
+ end
+
+ defp do_load_resolv do
+ # It could be we haven't read domain name from resolv file yet
+ :ok = :inet_config.do_load_resolv(:os.type(), :longnames)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/os_pid.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/os_pid.ex
new file mode 100644
index 0000000000..0b53d59748
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/os_pid.ex
@@ -0,0 +1,56 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.OsPid do
+ @external_process_check_interval 1000
+
+ @pid_regex ~r/^\s*(?<pid>\d+)/
+
+ #
+ # API
+ #
+
+ def wait_for_os_process_death(pid) do
+ case :rabbit_misc.is_os_process_alive(pid) do
+ true ->
+ :timer.sleep(@external_process_check_interval)
+ wait_for_os_process_death(pid)
+
+ false ->
+ :ok
+ end
+ end
+
+ def read_pid_from_file(pidfile_path, should_wait) do
+ case {:file.read_file(pidfile_path), should_wait} do
+ {{:ok, contents}, _} ->
+ pid_regex = Regex.recompile!(@pid_regex)
+
+ case Regex.named_captures(pid_regex, contents)["pid"] do
+ # e.g. the file is empty
+ nil ->
+ {:error, :could_not_read_pid_from_file, {:contents, contents}}
+
+ pid_string ->
+ try do
+ {pid, _remainder} = Integer.parse(pid_string)
+ pid
+ rescue
+ _e in ArgumentError ->
+ {:error, {:could_not_read_pid_from_file, {:contents, contents}}}
+ end
+ end
+
+ # file does not exist, wait and re-check
+ {{:error, :enoent}, true} ->
+ :timer.sleep(@external_process_check_interval)
+ read_pid_from_file(pidfile_path, should_wait)
+
+ {{:error, details}, _} ->
+ {:error, :could_not_read_pid_from_file, details}
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/output.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/output.ex
new file mode 100644
index 0000000000..1b2436cba4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/output.ex
@@ -0,0 +1,72 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Output do
+ def format_output(:ok, _, _) do
+ :ok
+ end
+
+ # the command intends to produce no output
+ def format_output({:ok, nil}, _, _) do
+ :ok
+ end
+
+ def format_output({:ok, :check_passed}, _, _) do
+ :ok
+ end
+
+ def format_output({:ok, output}, formatter, options) do
+ {:ok, formatter.format_output(output, options)}
+ end
+
+ def format_output({:stream, stream}, formatter, options) do
+ {:stream, formatter.format_stream(stream, options)}
+ end
+
+ def print_output(output, printer, options) do
+ {:ok, printer_state} = printer.init(options)
+ exit_code = print_output_0(output, printer, printer_state)
+ printer.finish(printer_state)
+ exit_code
+ end
+
+ def print_output_0(:ok, printer, printer_state) do
+ printer.print_ok(printer_state)
+ :ok
+ end
+
+ # the command intends to produce no output
+ def print_output_0({:ok, nil}, _printer, _printer_state) do
+ :ok
+ end
+
+ def print_output_0({:ok, :check_passed}, _printer, _printer_state) do
+ :ok
+ end
+
+ def print_output_0({:ok, single_value}, printer, printer_state) do
+ printer.print_output(single_value, printer_state)
+ :ok
+ end
+
+ def print_output_0({:stream, stream}, printer, printer_state) do
+ case print_output_stream(stream, printer, printer_state) do
+ :ok -> :ok
+ {:error, _} = err -> err
+ end
+ end
+
+ def print_output_stream(stream, printer, printer_state) do
+ Enum.reduce_while(stream, :ok, fn
+ {:error, err}, _ ->
+ {:halt, {:error, err}}
+
+ val, _ ->
+ printer.print_output(val, printer_state)
+ {:cont, :ok}
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/parser.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/parser.ex
new file mode 100644
index 0000000000..28c4df2aa4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/parser.ex
@@ -0,0 +1,311 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Parser do
+ alias RabbitMQ.CLI.{CommandBehaviour, FormatterBehaviour}
+ alias RabbitMQ.CLI.Core.{CommandModules, Config}
+
+ def default_switches() do
+ [
+ node: :atom,
+ quiet: :boolean,
+ silent: :boolean,
+ dry_run: :boolean,
+ vhost: :string,
+ # for backwards compatibility,
+ # not all commands support timeouts
+ timeout: :integer,
+ longnames: :boolean,
+ formatter: :string,
+ printer: :string,
+ file: :string,
+ script_name: :atom,
+ rabbitmq_home: :string,
+ mnesia_dir: :string,
+ plugins_dir: :string,
+ enabled_plugins_file: :string,
+ aliases_file: :string,
+ erlang_cookie: :atom,
+ help: :boolean,
+ print_stacktrace: :boolean
+ ]
+ end
+
+ def default_aliases() do
+ [
+ p: :vhost,
+ n: :node,
+ q: :quiet,
+ s: :silent,
+ l: :longnames,
+ # for backwards compatibility,
+ # not all commands support timeouts
+ t: :timeout,
+ "?": :help
+ ]
+ end
+
+ @spec parse([String.t()]) ::
+ {command :: :no_command | atom() | {:suggest, String.t()}, command_name :: String.t(),
+ arguments :: [String.t()], options :: map(),
+ invalid :: [{String.t(), String.t() | nil}]}
+
+ def parse(input) do
+ {parsed_args, options, invalid} = parse_global(input)
+ {command_name, command_module, arguments} = look_up_command(parsed_args, options)
+
+ case command_module do
+ nil ->
+ {:no_command, command_name, arguments, options, invalid}
+
+ {:suggest, _} = suggest ->
+ {suggest, command_name, arguments, options, invalid}
+
+ {:alias, alias_module, alias_content} ->
+ {[_alias_command_name | cmd_arguments], cmd_options, cmd_invalid} =
+ parse_alias(input, command_name, alias_module, alias_content, options)
+
+ {alias_module, command_name, cmd_arguments, cmd_options, cmd_invalid}
+
+ command_module when is_atom(command_module) ->
+ {[^command_name | cmd_arguments], cmd_options, cmd_invalid} =
+ parse_command_specific(input, command_module, options)
+
+ {command_module, command_name, cmd_arguments, cmd_options, cmd_invalid}
+ end
+ end
+
+ def command_suggestion(_cmd_name, empty) when empty == %{} do
+ nil
+ end
+ def command_suggestion(typed, module_map) do
+ RabbitMQ.CLI.AutoComplete.suggest_command(typed, module_map)
+ end
+
+ defp look_up_command(parsed_args, options) do
+ case parsed_args do
+ [cmd_name | arguments] ->
+ ## This is an optimisation for pluggable command discovery.
+ ## Most of the time a command will be from rabbitmqctl application
+ ## so there is not point in scanning plugins for potential commands
+ CommandModules.load_core(options)
+ core_commands = CommandModules.module_map_core()
+
+ command =
+ case core_commands[cmd_name] do
+ nil ->
+ CommandModules.load(options)
+ module_map = CommandModules.module_map()
+
+ module_map[cmd_name] ||
+ command_alias(cmd_name, module_map, options) ||
+ command_suggestion(cmd_name, module_map)
+
+ c ->
+ c
+ end
+
+ {cmd_name, command, arguments}
+
+ [] ->
+ {"", nil, []}
+ end
+ end
+
+ defp command_alias(cmd_name, module_map, options) do
+ aliases = load_aliases(options)
+
+ case aliases[cmd_name] do
+ nil ->
+ nil
+
+ [alias_cmd_name | _] = alias_content ->
+ case module_map[alias_cmd_name] do
+ nil -> nil
+ module -> {:alias, module, alias_content}
+ end
+ end
+ end
+
+ defp load_aliases(options) do
+ aliases_file = Config.get_option(:aliases_file, options)
+
+ case aliases_file && File.read(aliases_file) do
+ ## No aliases file
+ nil ->
+ %{}
+
+ {:ok, content} ->
+ String.split(content, "\n")
+ |> Enum.reduce(
+ %{},
+ fn str, acc ->
+ case String.split(str, "=", parts: 2) do
+ [alias_name, alias_string] ->
+ Map.put(acc, String.trim(alias_name), OptionParser.split(alias_string))
+
+ _ ->
+ acc
+ end
+ end
+ )
+
+ {:error, err} ->
+ IO.puts(:stderr, "Error reading aliases file #{aliases_file}: #{err}")
+ %{}
+ end
+ end
+
+ defp parse_alias(input, command_name, module, alias_content, options) do
+ {pre_command_options, tail, invalid} = parse_global_head(input)
+ [^command_name | other] = tail
+ aliased_input = alias_content ++ other
+ {args, options, command_invalid} = parse_command_specific(aliased_input, module, options)
+ merged_options = Map.merge(options, pre_command_options)
+ {args, merged_options, command_invalid ++ invalid}
+ end
+
+ def parse_command_specific(input, command, options \\ %{}) do
+ formatter = Config.get_formatter(command, options)
+
+ switches = build_switches(default_switches(), command, formatter)
+ aliases = build_aliases(default_aliases(), command, formatter)
+ parse_generic(input, switches, aliases)
+ end
+
+ def parse_global_head(input) do
+ switches = default_switches()
+ aliases = default_aliases()
+
+ {options, tail, invalid} =
+ OptionParser.parse_head(
+ input,
+ strict: switches,
+ aliases: aliases,
+ allow_nonexistent_atoms: true
+ )
+
+ norm_options = normalize_options(options, switches) |> Map.new()
+ {norm_options, tail, invalid}
+ end
+
+ def parse_global(input) do
+ switches = default_switches()
+ aliases = default_aliases()
+ parse_generic(input, switches, aliases)
+ end
+
+ defp parse_generic(input, switches, aliases) do
+ {options, args, invalid} =
+ OptionParser.parse(
+ input,
+ strict: switches,
+ aliases: aliases,
+ allow_nonexistent_atoms: true
+ )
+
+ norm_options = normalize_options(options, switches) |> Map.new()
+ {args, norm_options, invalid}
+ end
+
+ defp build_switches(default, command, formatter) do
+ command_switches = CommandBehaviour.switches(command)
+ formatter_switches = FormatterBehaviour.switches(formatter)
+
+ assert_no_conflict(
+ command,
+ command_switches,
+ formatter_switches,
+ :redefining_formatter_switches
+ )
+
+ merge_if_different(
+ default,
+ formatter_switches,
+ {:formatter_invalid,
+ {formatter, {:redefining_global_switches, default, formatter_switches}}}
+ )
+ |> merge_if_different(
+ command_switches,
+ {:command_invalid, {command, {:redefining_global_switches, default, command_switches}}}
+ )
+ end
+
+ defp assert_no_conflict(command, command_fields, formatter_fields, err) do
+ merge_if_different(
+ formatter_fields,
+ command_fields,
+ {:command_invalid, {command, {err, formatter_fields, command_fields}}}
+ )
+
+ :ok
+ end
+
+ defp build_aliases(default, command, formatter) do
+ command_aliases = CommandBehaviour.aliases(command)
+ formatter_aliases = FormatterBehaviour.aliases(formatter)
+
+ assert_no_conflict(command, command_aliases, formatter_aliases, :redefining_formatter_aliases)
+
+ merge_if_different(
+ default,
+ formatter_aliases,
+ {:formatter_invalid, {command, {:redefining_global_aliases, default, formatter_aliases}}}
+ )
+ |> merge_if_different(
+ command_aliases,
+ {:command_invalid, {command, {:redefining_global_aliases, default, command_aliases}}}
+ )
+ end
+
+ defp merge_if_different(default, specific, error) do
+ case keyword_intersect(default, specific) do
+ [] ->
+ Keyword.merge(default, specific)
+
+ conflicts ->
+ # if all conflicting keys are of the same type,
+ # that's acceptable
+ case Enum.all?(
+ conflicts,
+ fn c ->
+ Keyword.get(default, c) == Keyword.get(specific, c)
+ end
+ ) do
+ true -> Keyword.merge(default, specific)
+ false -> exit(error)
+ end
+ end
+ end
+
+ defp keyword_intersect(one, two) do
+ one_keys = MapSet.new(Keyword.keys(one))
+ two_keys = MapSet.new(Keyword.keys(two))
+ intersection = MapSet.intersection(one_keys, two_keys)
+
+ case Enum.empty?(intersection) do
+ true -> []
+ false -> MapSet.to_list(intersection)
+ end
+ end
+
+ defp normalize_options(options, switches) do
+ Enum.map(
+ options,
+ fn {key, option} ->
+ {key, normalize_type(option, switches[key])}
+ end
+ )
+ end
+
+ defp normalize_type(value, :atom) when is_binary(value) do
+ String.to_atom(value)
+ end
+
+ defp normalize_type(value, _type) do
+ value
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/paths.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/paths.ex
new file mode 100644
index 0000000000..0e90834771
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/paths.ex
@@ -0,0 +1,55 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Paths do
+ alias RabbitMQ.CLI.Core.Config
+ import RabbitMQ.CLI.Core.Platform
+
+ def plugins_dir(_, opts) do
+ plugins_dir(opts)
+ end
+
+ def plugins_dir(opts) do
+ case Config.get_option(:plugins_dir, opts) do
+ nil ->
+ {:error, :no_plugins_dir}
+
+ dir ->
+ paths = String.split(to_string(dir), path_separator())
+
+ case Enum.any?(paths, &File.dir?/1) do
+ true -> {:ok, dir}
+ false -> {:error, :plugins_dir_does_not_exist}
+ end
+ end
+ end
+
+ def require_mnesia_dir(opts) do
+ case Application.get_env(:mnesia, :dir) do
+ nil ->
+ case Config.get_option(:mnesia_dir, opts) do
+ nil -> {:error, :mnesia_dir_not_found}
+ val -> Application.put_env(:mnesia, :dir, to_charlist(val))
+ end
+
+ _ ->
+ :ok
+ end
+ end
+
+ def require_feature_flags_file(opts) do
+ case Application.get_env(:rabbit, :feature_flags_file) do
+ nil ->
+ case Config.get_option(:feature_flags_file, opts) do
+ nil -> {:error, :feature_flags_file_not_found}
+ val -> Application.put_env(:rabbit, :feature_flags_file, to_charlist(val))
+ end
+
+ _ ->
+ :ok
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/platform.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/platform.ex
new file mode 100644
index 0000000000..561b2adb58
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/platform.ex
@@ -0,0 +1,37 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Platform do
+ def path_separator() do
+ case :os.type() do
+ {:unix, _} -> ":"
+ {:win32, _} -> ";"
+ end
+ end
+
+ def line_separator() do
+ case :os.type() do
+ {:unix, _} -> "\n"
+ {:win32, _} -> "\r\n"
+ end
+ end
+
+ def os_name({:unix, :linux}) do
+ "Linux"
+ end
+ def os_name({:unix, :darwin}) do
+ "macOS"
+ end
+ def os_name({:unix, :freebsd}) do
+ "FreeBSD"
+ end
+ def os_name({:unix, name}) do
+ name |> to_string |> String.capitalize
+ end
+ def os_name({:win32, _}) do
+ "Windows"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_running.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_running.ex
new file mode 100644
index 0000000000..7f5337a6e7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_running.ex
@@ -0,0 +1,17 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be running
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.RequiresRabbitAppRunning do
+ defmacro __using__(_) do
+ quote do
+ def validate_execution_environment(args, opts) do
+ RabbitMQ.CLI.Core.Validators.rabbit_is_running(args, opts)
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_stopped.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_stopped.ex
new file mode 100644
index 0000000000..48b2b6dcd0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/requires_rabbit_app_stopped.ex
@@ -0,0 +1,17 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Should be used by commands that require rabbit app to be stopped
+# but need no other execution environment validators.
+defmodule RabbitMQ.CLI.Core.RequiresRabbitAppStopped do
+ defmacro __using__(_) do
+ quote do
+ def validate_execution_environment(args, opts) do
+ RabbitMQ.CLI.Core.Validators.rabbit_is_not_running(args, opts)
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/validators.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/validators.ex
new file mode 100644
index 0000000000..666d7af065
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/validators.ex
@@ -0,0 +1,115 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Provides common validation functions.
+defmodule RabbitMQ.CLI.Core.Validators do
+ alias RabbitMQ.CLI.Core.Helpers
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+
+ def chain([validator | rest], args) do
+ case apply(validator, args) do
+ :ok -> chain(rest, args)
+ {:ok, _} -> chain(rest, args)
+ {:validation_failure, err} -> {:validation_failure, err}
+ {:error, err} -> {:validation_failure, err}
+ end
+ end
+
+ def chain([], _) do
+ :ok
+ end
+
+ def validate_step(:ok, step) do
+ case step.() do
+ {:error, err} -> {:validation_failure, err}
+ _ -> :ok
+ end
+ end
+
+ def validate_step({:validation_failure, err}, _) do
+ {:validation_failure, err}
+ end
+
+ def node_is_not_running(_, %{node: node_name}) do
+ case Helpers.node_running?(node_name) do
+ true -> {:validation_failure, :node_running}
+ false -> :ok
+ end
+ end
+
+ def node_is_running(_, %{node: node_name}) do
+ case Helpers.node_running?(node_name) do
+ false -> {:validation_failure, :node_not_running}
+ true -> :ok
+ end
+ end
+
+ def mnesia_dir_is_set(_, opts) do
+ case require_mnesia_dir(opts) do
+ :ok -> :ok
+ {:error, err} -> {:validation_failure, err}
+ end
+ end
+
+ def feature_flags_file_is_set(_, opts) do
+ case require_feature_flags_file(opts) do
+ :ok -> :ok
+ {:error, err} -> {:validation_failure, err}
+ end
+ end
+
+ def rabbit_is_loaded(_, opts) do
+ case require_rabbit(opts) do
+ :ok -> :ok
+ {:error, err} -> {:validation_failure, err}
+ end
+ end
+
+ def rabbit_app_running?(%{node: node, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node, :rabbit, :is_running, [], timeout) do
+ true -> true
+ false -> false
+ other -> {:error, other}
+ end
+ end
+
+ def rabbit_app_running?(_, opts) do
+ rabbit_app_running?(opts)
+ end
+
+ def rabbit_is_running(args, opts) do
+ case rabbit_app_state(args, opts) do
+ :running -> :ok
+ :stopped -> {:validation_failure, :rabbit_app_is_stopped}
+ other -> other
+ end
+ end
+
+ def rabbit_is_running_or_offline_flag_used(_args, %{offline: true}) do
+ :ok
+ end
+
+ def rabbit_is_running_or_offline_flag_used(args, opts) do
+ rabbit_is_running(args, opts)
+ end
+
+ def rabbit_is_not_running(args, opts) do
+ case rabbit_app_state(args, opts) do
+ :running -> {:validation_failure, :rabbit_app_is_running}
+ :stopped -> :ok
+ other -> other
+ end
+ end
+
+ def rabbit_app_state(_, opts) do
+ case rabbit_app_running?(opts) do
+ true -> :running
+ false -> :stopped
+ {:error, err} -> {:error, err}
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/core/version.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/version.ex
new file mode 100644
index 0000000000..bd5a24f9a0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/core/version.ex
@@ -0,0 +1,24 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Core.Version do
+ @default_timeout 30_000
+
+ def local_version do
+ to_string(:rabbit_misc.version())
+ end
+
+
+ def remote_version(node_name) do
+ remote_version(node_name, @default_timeout)
+ end
+ def remote_version(node_name, timeout) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_misc, :version, [], timeout) do
+ {:badrpc, _} = err -> err
+ val -> val
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_user_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_user_command.ex
new file mode 100644
index 0000000000..514922cac9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_user_command.ex
@@ -0,0 +1,98 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers, Input}
+ import RabbitMQ.CLI.Core.Config, only: [output_less?: 1]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
+ def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
+ def validate([_], _), do: :ok
+ def validate(["", _], _) do
+ {:validation_failure, {:bad_argument, "user cannot be an empty string"}}
+ end
+ def validate([_, _], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name} = opts) do
+ # note: blank passwords are currently allowed, they make sense
+ # e.g. when a user only authenticates using X.509 certificates.
+ # Credential validators can be used to require passwords of a certain length
+ # or following a certain pattern. This is a core server responsibility. MK.
+ case Input.infer_password("Password: ", opts) do
+ :eof -> {:error, :not_enough_args}
+ password -> :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :add_user,
+ [username, password, Helpers.cli_acting_user()]
+ )
+ end
+ end
+ def run([username, password], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :add_user,
+ [username, password, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, :not_enough_args}, _) do
+ {:error, ExitCodes.exit_dataerr(), "Password is not provided via argument or stdin"}
+ end
+ def output({:error, {:user_already_exists, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} already exists"}}
+ end
+ def output({:error, {:user_already_exists, username}}, _) do
+ {:error, ExitCodes.exit_software(), "User \"#{username}\" already exists"}
+ end
+ def output(:ok, %{formatter: "json", node: node_name}) do
+ m = %{
+ "status" => "ok",
+ "node" => node_name,
+ "message" => "Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more."
+ }
+ {:ok, m}
+ end
+ def output(:ok, opts) do
+ case output_less?(opts) do
+ true ->
+ :ok
+ false ->
+ {:ok, "Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more."}
+ end
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "add_user <username> <password>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Self-explanatory"],
+ ["<password>", "Password this user will authenticate with. Use a blank string to disable password-based authentication."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description() do
+ "Creates a new user in the internal database. This user will have no permissions for any virtual hosts by default."
+ end
+
+ def banner([username | _], _), do: "Adding user \"#{username}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_vhost_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_vhost_command.ex
new file mode 100644
index 0000000000..04c1e61106
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_vhost_command.ex
@@ -0,0 +1,62 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AddVhostCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [description: :string,
+ tags: :string]
+ def aliases(), do: [d: :description]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{description: "", tags: ""}, opts)}
+ end
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([vhost], %{node: node_name, description: desc, tags: tags}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost, :add, [vhost, desc, parse_tags(tags), Helpers.cli_acting_user()])
+ end
+ def run([vhost], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost, :add, [vhost, Helpers.cli_acting_user()])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "add_vhost <vhost> [--description <description> --tags \"<tag1>,<tag2>,<...>\"]"
+
+ def usage_additional() do
+ [
+ ["<vhost>", "Virtual host name"],
+ ["--description <description>", "Virtual host description"],
+ ["--tags <tags>", "Command separated list of tags"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Creates a virtual host"
+
+ def banner([vhost], _), do: "Adding vhost \"#{vhost}\" ..."
+
+ #
+ # Implementation
+ #
+
+ def parse_tags(tags) do
+ String.split(tags, ",", trim: true)
+ |> Enum.map(&String.trim/1)
+ |> Enum.map(&String.to_atom/1)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/authenticate_user_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/authenticate_user_command.ex
new file mode 100644
index 0000000000..9913633b84
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/authenticate_user_command.ex
@@ -0,0 +1,79 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Input}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
+ def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
+ def validate([_], _), do: :ok
+ def validate([_, _], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([user], %{node: node_name} = opts) do
+ # note: blank passwords are currently allowed, they make sense
+ # e.g. when a user only authenticates using X.509 certificates.
+ # Credential validators can be used to require passwords of a certain length
+ # or following a certain pattern. This is a core server responsibility. MK.
+ case Input.infer_password("Password: ", opts) do
+ :eof -> {:error, :not_enough_args}
+ password -> :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_access_control,
+ :check_user_pass_login,
+ [user, password]
+ )
+ end
+ end
+ def run([user, password], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_access_control,
+ :check_user_pass_login,
+ [user, password]
+ )
+ end
+
+ def usage, do: "authenticate_user <username> <password>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Username to use"],
+ ["<password>", "Password to use. Can be entered via stdin in interactive mode."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Attempts to authenticate a user. Exits with a non-zero code if authentication fails."
+
+ def banner([username | _], _), do: "Authenticating user \"#{username}\" ..."
+
+ def output({:error, :not_enough_args}, _) do
+ {:error, ExitCodes.exit_software(), "Password is not provided via argument or stdin"}
+ end
+ def output({:refused, user, msg, args}, _) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_dataerr(),
+ "Error: failed to authenticate user \"#{user}\"\n" <>
+ to_string(:io_lib.format(msg, args))}
+ end
+ def output({:ok, _user}, _) do
+ {:ok, "Success"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/autocomplete_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/autocomplete_command.ex
new file mode 100644
index 0000000000..19deb74f79
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/autocomplete_command.ex
@@ -0,0 +1,53 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AutocompleteCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.{Config, DocGuide}
+
+ def scopes(), do: [:ctl, :diagnostics, :plugins, :queues]
+
+ def distribution(_), do: :none
+
+ def merge_defaults(args, opts) do
+ # enforce --silent as shell completion does not
+ # expect to receive any additional output, so the command
+ # is not really interactive
+ {args, Map.merge(opts, %{silent: true})}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+
+ def run(args, %{script_name: script_name}) do
+ {:stream, RabbitMQ.CLI.AutoComplete.complete(script_name, args)}
+ end
+ def run(args, opts) do
+ script_name = Config.get_system_option(:script_name, opts)
+
+ {:stream, RabbitMQ.CLI.AutoComplete.complete(script_name, args)}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "autocomplete [prefix]"
+ end
+
+ def banner(_args, _opts) do
+ nil
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.cli()
+ ]
+ end
+
+ def help_section(), do: :help
+
+ def description(), do: "Provides command name autocomplete variants"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_online_nodes_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_online_nodes_command.ex
new file mode 100644
index 0000000000..f0d1df6a02
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_online_nodes_command.ex
@@ -0,0 +1,62 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AwaitOnlineNodesCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 300_000
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(opts, %{timeout: timeout})}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsOnePositiveIntegerArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([count], %{node: node_name, timeout: timeout}) do
+ {n, _} = Integer.parse(count)
+ :rabbit_misc.rpc_call(node_name, :rabbit_nodes, :await_running_count, [n, timeout])
+ end
+
+ def output({:error, :timeout}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: timed out while waiting. Not enough nodes joined #{node_name}'s cluster."}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([count], %{node: node_name, timeout: timeout}) when is_number(timeout) do
+ "Will wait for at least #{count} nodes to join the cluster of #{node_name}. Timeout: #{
+ trunc(timeout / 1000)
+ } seconds."
+ end
+
+ def banner([count], %{node: node_name, timeout: _timeout}) do
+ "Will wait for at least #{count} nodes to join the cluster of #{node_name}."
+ end
+
+ def usage() do
+ "await_online_nodes <count>"
+ end
+
+ def usage_additional() do
+ [
+ ["<count>", "how many cluster members must be up in order for this command to exit. When <count> is 1, always exits immediately."]
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Waits for <count> nodes to join the cluster"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_startup_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_startup_command.ex
new file mode 100644
index 0000000000..9a898224ce
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/await_startup_command.ex
@@ -0,0 +1,45 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.AwaitStartupCommand do
+ @moduledoc """
+ Waits until target node is fully booted. If the node is already running,
+ returns immediately.
+
+ This command is meant to be used when automating deployments.
+ See also `AwaitOnlineNodesCommand`.
+ """
+
+ import RabbitMQ.CLI.Core.Config, only: [output_less?: 1]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 300_000
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{timeout: @default_timeout}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout} = opts) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :await_startup, [
+ node_name,
+ not output_less?(opts),
+ timeout
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "await_startup"
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Waits for the RabbitMQ application to start on the target node"
+
+ def banner(_, _), do: nil
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cancel_sync_queue_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cancel_sync_queue_command.ex
new file mode 100644
index 0000000000..2858040039
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cancel_sync_queue_command.ex
@@ -0,0 +1,52 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.CancelSyncQueueCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([queue], %{vhost: vhost, node: node_name}) do
+ :rpc.call(
+ node_name,
+ :rabbit_mirror_queue_misc,
+ :cancel_sync_queue,
+ [:rabbit_misc.r(vhost, :queue, queue)],
+ :infinity
+ )
+ end
+
+ def usage, do: "cancel_sync_queue [--vhost <vhost>] <queue>"
+
+ def usage_additional() do
+ [
+ ["<queue>", "Queue name"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.mirroring()
+ ]
+ end
+
+ def help_section(), do: :replication
+
+ def description(), do: "Instructs a synchronising mirrored queue to stop synchronising itself"
+
+ def banner([queue], %{vhost: vhost, node: _node}) do
+ "Stopping synchronising queue '#{queue}' in vhost '#{vhost}' ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_cluster_node_type_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_cluster_node_type_command.ex
new file mode 100644
index 0000000000..93fc9c7da0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_cluster_node_type_command.ex
@@ -0,0 +1,87 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ChangeClusterNodeTypeCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, opts}
+ end
+
+ def validate([], _), do: {:validation_failure, :not_enough_args}
+
+ # node type
+ def validate(["disc"], _), do: :ok
+ def validate(["disk"], _), do: :ok
+ def validate(["ram"], _), do: :ok
+
+ def validate([_], _),
+ do: {:validation_failure, {:bad_argument, "The node type must be either disc or ram."}}
+
+ def validate(_, _), do: {:validation_failure, :too_many_args}
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppStopped
+
+ def run([node_type_arg], %{node: node_name}) do
+ normalized_type = normalize_type(String.to_atom(node_type_arg))
+ current_type = :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :node_type, [])
+
+ case current_type do
+ ^normalized_type ->
+ {:ok, "Node type is already #{normalized_type}"}
+
+ _ ->
+ :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :change_cluster_node_type, [
+ normalized_type
+ ])
+ end
+ end
+
+ def usage() do
+ "change_cluster_node_type <disc | ram>"
+ end
+
+ def usage_additional() do
+ [
+ ["<disc | ram>", "New node type"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Changes the type of the cluster node"
+
+ def banner([node_type], %{node: node_name}) do
+ "Turning #{node_name} into a #{node_type} node"
+ end
+
+ def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ defp normalize_type(:ram) do
+ :ram
+ end
+
+ defp normalize_type(:disc) do
+ :disc
+ end
+
+ defp normalize_type(:disk) do
+ :disc
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_password_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_password_command.ex
new file mode 100644
index 0000000000..b0dec0a824
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/change_password_command.ex
@@ -0,0 +1,76 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ChangePasswordCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers, Input}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
+ def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
+ def validate([_], _), do: :ok
+ def validate([_, _], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name} = opts) do
+ # note: blank passwords are currently allowed, they make sense
+ # e.g. when a user only authenticates using X.509 certificates.
+ # Credential validators can be used to require passwords of a certain length
+ # or following a certain pattern. This is a core server responsibility. MK.
+ case Input.infer_password("Password: ", opts) do
+ :eof -> {:error, :not_enough_args}
+ password -> :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :change_password,
+ [username, password, Helpers.cli_acting_user()]
+ )
+ end
+ end
+ def run([username, password], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :change_password,
+ [username, password, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, :not_enough_args}, _) do
+ {:error, ExitCodes.exit_software(), "Password is not provided via argument or stdin"}
+ end
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exists"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User \"#{username}\" does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "change_password <username> <password>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Name of the user whose password should be changed"],
+ ["<password>", "New password to set"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Changes the user password"
+
+ def banner([user | _], _), do: "Changing password for user \"#{user}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_global_parameter_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_global_parameter_command.ex
new file mode 100644
index 0000000000..c5cedeb96a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_global_parameter_command.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearGlobalParameterCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([key], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :clear_global,
+ [key, Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "clear_global_parameter <name>"
+
+ def usage_additional() do
+ [
+ ["<name>", "parameter name (identifier)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Clears a global runtime parameter"
+
+ def banner([key], _) do
+ "Clearing global runtime parameter \"#{key}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_operator_policy_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_operator_policy_command.ex
new file mode 100644
index 0000000000..4b77d4cb38
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_operator_policy_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearOperatorPolicyCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([key], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_policy, :delete_op, [
+ vhost,
+ key,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ def usage, do: "clear_operator_policy [--vhost <vhost>] <name>"
+
+ def usage_additional() do
+ [
+ ["<name>", "policy name (identifier)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Clears an operator policy"
+
+ def banner([key], %{vhost: vhost}) do
+ "Clearing operator policy \"#{key}\" on vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_parameter_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_parameter_command.ex
new file mode 100644
index 0000000000..3997b1b61f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_parameter_command.ex
@@ -0,0 +1,60 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearParameterCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ def validate(args, _) when is_list(args) and length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([_, _], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([component_name, key], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :clear,
+ [vhost, component_name, key, Helpers.cli_acting_user()]
+ )
+ end
+
+ def usage, do: "clear_parameter [--vhost <vhost>] <component_name> <name>"
+
+ def usage_additional() do
+ [
+ ["<component_name>", "component name"],
+ ["<name>", "parameter name (identifier)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Clears a runtime parameter."
+
+ def banner([component_name, key], %{vhost: vhost}) do
+ "Clearing runtime parameter \"#{key}\" for component \"#{component_name}\" on vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_password_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_password_command.ex
new file mode 100644
index 0000000000..398af4813b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_password_command.ex
@@ -0,0 +1,46 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearPasswordCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_user] = args, %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :clear_password,
+ args ++ [Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "clear_password <username>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Name of the user whose password should be cleared"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Clears (resets) password and disables password login for a user"
+
+ def banner([user], _), do: "Clearing password for user \"#{user}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_permissions_command.ex
new file mode 100644
index 0000000000..2fd129fffa
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_permissions_command.ex
@@ -0,0 +1,61 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearPermissionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :clear_permissions, [
+ username,
+ vhost,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "Virtual host #{vhost} does not exist"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User #{username} does not exist"}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, _) do
+ {:error, "Virtual host #{vhost} does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "clear_permissions [--vhost <vhost>] <username>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Name of the user whose permissions should be revoked"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Revokes user permissions for a vhost"
+
+ def banner([username], %{vhost: vhost}) do
+ "Clearing permissions for user \"#{username}\" in vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_policy_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_policy_command.ex
new file mode 100644
index 0000000000..057c2e8c24
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_policy_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearPolicyCommand do
+ alias RabbitMQ.CLI.Core.{Helpers, DocGuide}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([key], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_policy, :delete, [
+ vhost,
+ key,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ def usage, do: "clear_policy [--vhost <vhost>] <name>"
+
+ def usage_additional() do
+ [
+ ["<name>", "Name of policy to clear (remove)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Clears (removes) a policy"
+
+ def banner([key], %{vhost: vhost}) do
+ "Clearing policy \"#{key}\" on vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_topic_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_topic_permissions_command.ex
new file mode 100644
index 0000000000..5d0b249db6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_topic_permissions_command.ex
@@ -0,0 +1,85 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearTopicPermissionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([_], _), do: :ok
+ def validate([_, _], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :clear_topic_permissions, [
+ username,
+ vhost,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ def run([username, exchange], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :clear_topic_permissions, [
+ username,
+ vhost,
+ exchange,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "Virtual host #{vhost} does not exist"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User #{username} does not exist"}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, _) do
+ {:error, "Virtual host #{vhost} does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "clear_topic_permissions [--vhost <vhost>] <username> [<exchange>]"
+
+ def usage_additional() do
+ [
+ ["<username>", "Name of the user whose topic permissions should be revoked"],
+ ["<exchange>", "Exchange the permissions are for"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Clears user topic permissions for a vhost or exchange"
+
+ def banner([username], %{vhost: vhost}) do
+ "Clearing topic permissions for user \"#{username}\" in vhost \"#{vhost}\" ..."
+ end
+
+ def banner([username, exchange], %{vhost: vhost}) do
+ "Clearing topic permissions on \"#{exchange}\" for user \"#{username}\" in vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_user_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_user_limits_command.ex
new file mode 100644
index 0000000000..301de613bb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_user_limits_command.ex
@@ -0,0 +1,50 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearUserLimitsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+
+ def run([username, limit_type], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :clear_user_limits, [
+ username,
+ limit_type,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def usage, do: "clear_user_limits username <limit_type> | all"
+
+ def usage_additional() do
+ [
+ ["<limit_type>", "Limit type, must be max-connections or max-channels"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Clears user connection/channel limits"
+
+ def banner([username, "all"], %{}) do
+ "Clearing all limits for user \"#{username}\" ..."
+ end
+ def banner([username, limit_type], %{}) do
+ "Clearing \"#{limit_type}\" limit for user \"#{username}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_vhost_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_vhost_limits_command.ex
new file mode 100644
index 0000000000..a73f0ff670
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/clear_vhost_limits_command.ex
@@ -0,0 +1,43 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClearVhostLimitsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost_limit, :clear, [
+ vhost,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def usage, do: "clear_vhost_limits [--vhost <vhost>]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Clears virtual host limits"
+
+ def banner([], %{vhost: vhost}) do
+ "Clearing vhost \"#{vhost}\" limits ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_all_connections_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_all_connections_command.ex
new file mode 100644
index 0000000000..d4c5b5f17a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_all_connections_command.ex
@@ -0,0 +1,124 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.CloseAllConnectionsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [global: :boolean, per_connection_delay: :integer, limit: :integer]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{global: false, vhost: "/", per_connection_delay: 0, limit: 0}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([explanation], %{
+ node: node_name,
+ vhost: vhost,
+ global: global_opt,
+ per_connection_delay: delay,
+ limit: limit
+ }) do
+ conns =
+ case global_opt do
+ false ->
+ per_vhost =
+ :rabbit_misc.rpc_call(node_name, :rabbit_connection_tracking, :list, [vhost])
+
+ apply_limit(per_vhost, limit)
+
+ true ->
+ :rabbit_misc.rpc_call(node_name, :rabbit_connection_tracking, :list_on_node, [node_name])
+ end
+
+ case conns do
+ {:badrpc, _} = err ->
+ err
+
+ _ ->
+ :rabbit_misc.rpc_call(
+ node_name,
+ # As of 3.7.15, this function was moved to the rabbit_connection_tracking module.
+ # rabbit_connection_tracking_handler still contains a delegating function with the same name.
+ # Continue using this one for now for maximum CLI/server version compatibility. MK.
+ :rabbit_connection_tracking_handler,
+ :close_connections,
+ [conns, explanation, delay]
+ )
+
+ {:ok, "Closed #{length(conns)} connections"}
+ end
+ end
+
+ def run(args, %{
+ node: node_name,
+ global: global_opt,
+ per_connection_delay: delay,
+ limit: limit
+ }) do
+ run(args, %{
+ node: node_name,
+ vhost: nil,
+ global: global_opt,
+ per_connection_delay: delay,
+ limit: limit
+ })
+ end
+
+ def output({:stream, stream}, _opts) do
+ {:stream, Stream.filter(stream, fn x -> x != :ok end)}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([explanation], %{node: node_name, global: true}) do
+ "Closing all connections to node #{node_name} (across all vhosts), reason: #{explanation}..."
+ end
+
+ def banner([explanation], %{vhost: vhost, limit: 0}) do
+ "Closing all connections in vhost #{vhost}, reason: #{explanation}..."
+ end
+
+ def banner([explanation], %{vhost: vhost, limit: limit}) do
+ "Closing #{limit} connections in vhost #{vhost}, reason: #{explanation}..."
+ end
+
+ def usage do
+ "close_all_connections [--vhost <vhost> --limit <limit>] [-n <node> --global] [--per-connection-delay <delay>] <explanation>"
+ end
+
+ def usage_additional do
+ [
+ ["--global", "consider connections across all virtual hosts"],
+ ["--limit <number>", "close up to this many connections"],
+ ["--per-connection-delay <milliseconds>", "inject a delay between closures"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.connections()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Instructs the broker to close all connections for the specified vhost or entire RabbitMQ node"
+
+ #
+ # Implementation
+ #
+
+ defp apply_limit(conns, 0) do
+ conns
+ end
+
+ defp apply_limit(conns, number) do
+ :lists.sublist(conns, number)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_connection_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_connection_command.ex
new file mode 100644
index 0000000000..371c582b19
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/close_connection_command.ex
@@ -0,0 +1,46 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.CloseConnectionCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([pid, explanation], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_networking, :close_connection, [
+ :rabbit_misc.string_to_pid(pid),
+ explanation
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "close_connection <connection pid> <explanation>"
+
+ def usage_additional do
+ [
+ ["<connection pid>", "connection identifier (Erlang PID), see list_connections"],
+ ["<explanation>", "reason for connection closure"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.connections()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Instructs the broker to close the connection associated with the Erlang process id"
+
+ def banner([pid, explanation], _), do: "Closing connection #{pid}, reason: #{explanation}..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cluster_status_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cluster_status_command.ex
new file mode 100644
index 0000000000..20f96e8075
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/cluster_status_command.ex
@@ -0,0 +1,285 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ClusterStatusCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.{Alarms, ANSI, Listeners, Platform, FeatureFlags}
+ import RabbitMQ.CLI.Core.Distribution, only: [per_node_timeout: 2]
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 60_000
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(%{timeout: timeout}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :status, []) do
+ {:badrpc, _} = err ->
+ err
+
+ status ->
+ case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :cluster_nodes, [:running]) do
+ {:badrpc, _} = err ->
+ err
+
+ {:error, {:corrupt_or_missing_cluster_files, _, _}} ->
+ {:error, "Could not read mnesia files containing cluster status"}
+
+ nodes ->
+ count = length(nodes)
+ alarms_by_node = Enum.map(nodes, fn n -> alarms_by_node(n, per_node_timeout(timeout, count)) end)
+ listeners_by_node = Enum.map(nodes, fn n -> listeners_of(n, per_node_timeout(timeout, count)) end)
+ versions_by_node = Enum.map(nodes, fn n -> versions_by_node(n, per_node_timeout(timeout, count)) end)
+ maintenance_status_by_node = Enum.map(nodes,
+ fn n -> maintenance_status_by_node(n, per_node_timeout(timeout, count)) end)
+
+ feature_flags = case :rabbit_misc.rpc_call(node_name, :rabbit_ff_extra, :cli_info, [], timeout) do
+ {:badrpc, {:EXIT, {:undef, _}}} -> []
+ {:badrpc, _} = err -> err
+ val -> val
+ end
+
+ status ++
+ [{:alarms, alarms_by_node}] ++
+ [{:listeners, listeners_by_node}] ++
+ [{:versions, versions_by_node}] ++
+ [{:maintenance_status, maintenance_status_by_node}] ++
+ [{:feature_flags, feature_flags}]
+ end
+ end
+ end
+
+ def output({:error, :timeout}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: timed out while waiting for a response from #{node_name}."}
+ end
+
+ def output(result, %{formatter: "erlang"}) do
+ {:ok, result}
+ end
+
+ def output(result, %{formatter: "json"}) when is_list(result) do
+ # format more data structures as map for sensible JSON output
+ m = result_map(result)
+ |> Map.update(:alarms, [], fn xs -> alarm_maps(xs) end)
+ |> Map.update(:listeners, %{}, fn m ->
+ Enum.map(m, fn {n, xs} -> {n, listener_maps(xs)} end) |> Enum.into(%{})
+ end)
+
+ {:ok, m}
+ end
+
+ def output(result, %{node: node_name}) when is_list(result) do
+ m = result_map(result)
+
+ cluster_name_section = [
+ "#{bright("Basics")}\n",
+ "Cluster name: #{m[:cluster_name]}"
+ ]
+
+ disk_nodes_section = [
+ "\n#{bright("Disk Nodes")}\n",
+ ] ++ node_lines(m[:disk_nodes])
+
+ ram_nodes_section = case m[:ram_nodes] do
+ [] -> []
+ xs -> [
+ "\n#{bright("RAM Nodes")}\n",
+ ] ++ node_lines(xs)
+ end
+
+ running_nodes_section = [
+ "\n#{bright("Running Nodes")}\n",
+ ] ++ node_lines(m[:running_nodes])
+
+ versions_section = [
+ "\n#{bright("Versions")}\n",
+ ] ++ version_lines(m[:versions])
+
+ alarms_section = [
+ "\n#{bright("Alarms")}\n",
+ ] ++ case m[:alarms] do
+ [] -> ["(none)"]
+ xs -> alarm_lines(xs, node_name)
+ end
+
+ partitions_section = [
+ "\n#{bright("Network Partitions")}\n"
+ ] ++ case map_size(m[:partitions]) do
+ 0 -> ["(none)"]
+ _ -> partition_lines(m[:partitions])
+ end
+
+ listeners_section = [
+ "\n#{bright("Listeners")}\n"
+ ] ++ case map_size(m[:listeners]) do
+ 0 -> ["(none)"]
+ _ -> Enum.reduce(m[:listeners], [], fn {node, listeners}, acc ->
+ acc ++ listener_lines(listeners, node)
+ end)
+ end
+
+ maintenance_section = [
+ "\n#{bright("Maintenance status")}\n",
+ ] ++ maintenance_lines(m[:maintenance_status])
+
+ feature_flags_section = [
+ "\n#{bright("Feature flags")}\n"
+ ] ++ case Enum.count(m[:feature_flags]) do
+ 0 -> ["(none)"]
+ _ -> feature_flag_lines(m[:feature_flags])
+ end
+
+ lines = cluster_name_section ++ disk_nodes_section ++ ram_nodes_section ++ running_nodes_section ++
+ versions_section ++ maintenance_section ++ alarms_section ++ partitions_section ++
+ listeners_section ++ feature_flags_section
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.String
+
+ def usage, do: "cluster_status"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering(),
+ DocGuide.cluster_formation(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Displays all the nodes in the cluster grouped by node type, together with the currently running nodes"
+
+ def banner(_, %{node: node_name}), do: "Cluster status of node #{node_name} ..."
+
+ #
+ # Implementation
+ #
+
+ defp result_map(result) do
+ # [{nodes,[{disc,[hare@warp10,rabbit@warp10]},{ram,[flopsy@warp10]}]},
+ # {running_nodes,[flopsy@warp10,hare@warp10,rabbit@warp10]},
+ # {cluster_name,<<"rabbit@localhost">>},
+ # {partitions,[{flopsy@warp10,[rabbit@vagrant]},
+ # {hare@warp10,[rabbit@vagrant]}]},
+ # {alarms,[{flopsy@warp10,[]},
+ # {hare@warp10,[]},
+ # {rabbit@warp10,[{resource_limit,memory,rabbit@warp10}]}]}]
+ %{
+ cluster_name: Keyword.get(result, :cluster_name),
+ disk_nodes: result |> Keyword.get(:nodes, []) |> Keyword.get(:disc, []),
+ ram_nodes: result |> Keyword.get(:nodes, []) |> Keyword.get(:ram, []),
+ running_nodes: result |> Keyword.get(:running_nodes, []) |> Enum.map(&to_string/1),
+ alarms: Keyword.get(result, :alarms) |> Keyword.values |> Enum.concat |> Enum.uniq,
+ maintenance_status: Keyword.get(result, :maintenance_status, []) |> Enum.into(%{}),
+ partitions: Keyword.get(result, :partitions, []) |> Enum.into(%{}),
+ listeners: Keyword.get(result, :listeners, []) |> Enum.into(%{}),
+ versions: Keyword.get(result, :versions, []) |> Enum.into(%{}),
+ feature_flags: Keyword.get(result, :feature_flags, []) |> Enum.map(fn ff -> Enum.into(ff, %{}) end)
+ }
+ end
+
+ defp alarms_by_node(node, timeout) do
+ alarms = case :rabbit_misc.rpc_call(to_atom(node), :rabbit, :alarms, [], timeout) do
+ {:badrpc, _} -> []
+ xs -> xs
+ end
+
+ {node, alarms}
+ end
+
+ defp listeners_of(node, timeout) do
+ # This may seem inefficient since this call returns all known listeners
+ # in the cluster, so why do we run it on every node? See the badrpc clause,
+ # some nodes may be inavailable or partitioned from other nodes. This way we
+ # gather as complete a picture as possible. MK.
+ listeners = case :rabbit_misc.rpc_call(to_atom(node), :rabbit_networking, :active_listeners, [], timeout) do
+ {:badrpc, _} -> []
+ xs -> xs
+ end
+
+ {node, listeners_on(listeners, node)}
+ end
+
+ defp versions_by_node(node, timeout) do
+ {rmq_name, rmq_vsn, otp_vsn} = case :rabbit_misc.rpc_call(
+ to_atom(node), :rabbit, :product_info, [], timeout) do
+ {:badrpc, _} ->
+ {nil, nil, nil}
+ map ->
+ %{:otp_release => otp} = map
+ name = case map do
+ %{:product_name => v} -> v
+ %{:product_base_name => v} -> v
+ end
+ vsn = case map do
+ %{:product_version => v} -> v
+ %{:product_base_version => v} -> v
+ end
+ {name, vsn, otp}
+ end
+
+ {node, %{rabbitmq_name: to_string(rmq_name), rabbitmq_version: to_string(rmq_vsn), erlang_version: to_string(otp_vsn)}}
+ end
+
+ defp maintenance_status_by_node(node, timeout) do
+ target = to_atom(node)
+ result = case :rabbit_misc.rpc_call(target,
+ :rabbit_maintenance, :status_local_read, [target], timeout) do
+ {:badrpc, _} -> "unknown"
+ :regular -> "not under maintenance"
+ :draining -> magenta("marked for maintenance")
+ # forward compatibility: should we figure out a way to know when
+ # draining completes (it involves inherently asynchronous cluster
+ # operations such as quorum queue leader re-election), we'd introduce
+ # a new state
+ :drained -> magenta("marked for maintenance")
+ value -> to_string(value)
+ end
+
+ {node, result}
+ end
+
+ defp node_lines(nodes) do
+ Enum.map(nodes, &to_string/1) |> Enum.sort
+ end
+
+ defp version_lines(mapping) do
+ Enum.map(mapping, fn {node, %{rabbitmq_name: rmq_name, rabbitmq_version: rmq_vsn, erlang_version: otp_vsn}} ->
+ "#{node}: #{rmq_name} #{rmq_vsn} on Erlang #{otp_vsn}"
+ end)
+ end
+
+ defp partition_lines(mapping) do
+ Enum.map(mapping, fn {node, unreachable_peers} -> "Node #{node} cannot communicate with #{Enum.join(unreachable_peers, ", ")}" end)
+ end
+
+ defp maintenance_lines(mapping) do
+ Enum.map(mapping, fn {node, status} -> "Node: #{node}, status: #{status}" end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex
new file mode 100644
index 0000000000..015617b102
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex
@@ -0,0 +1,116 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+alias RabbitMQ.CLI.Core.Helpers
+
+defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def switches() do
+ [
+ cipher: :atom,
+ hash: :atom,
+ iterations: :integer
+ ]
+ end
+
+ def distribution(_), do: :none
+
+ def merge_defaults(args, opts) do
+ with_defaults = Map.merge(%{
+ cipher: :rabbit_pbe.default_cipher(),
+ hash: :rabbit_pbe.default_hash(),
+ iterations: :rabbit_pbe.default_iterations()
+ }, opts)
+ {args, with_defaults}
+ end
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, {:not_enough_args, "Please provide a value to decode and a passphrase"}}
+ end
+
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(args, opts) when length(args) === 2 do
+ case {supports_cipher(opts.cipher), supports_hash(opts.hash), opts.iterations > 0} do
+ {false, _, _} ->
+ {:validation_failure, {:bad_argument, "The requested cipher is not supported"}}
+
+ {_, false, _} ->
+ {:validation_failure, {:bad_argument, "The requested hash is not supported"}}
+
+ {_, _, false} ->
+ {:validation_failure,
+ {:bad_argument,
+ "The requested number of iterations is incorrect (must be a positive integer)"}}
+
+ {true, true, true} ->
+ :ok
+ end
+ end
+
+ def run([value, passphrase], %{cipher: cipher, hash: hash, iterations: iterations}) do
+ try do
+ term_value = Helpers.evaluate_input_as_term(value)
+
+ term_to_decrypt =
+ case term_value do
+ {:encrypted, _} = encrypted ->
+ encrypted
+ _ ->
+ {:encrypted, term_value}
+ end
+
+ result = :rabbit_pbe.decrypt_term(cipher, hash, iterations, passphrase, term_to_decrypt)
+ {:ok, result}
+ catch
+ _, _ ->
+ {:error,
+ "Failed to decrypt the value. Things to check: is the passphrase correct? Are the cipher and hash algorithms the same as those used for encryption?"}
+ end
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def banner([_, _], _) do
+ "Decrypting value ..."
+ end
+
+ def usage, do: "decode value passphrase [--cipher <cipher>] [--hash <hash>] [--iterations <iterations>]"
+
+ def usage_additional() do
+ [
+ ["<value>", "config value to decode"],
+ ["<passphrase>", "passphrase to use with the config value encryption key"],
+ ["--cipher <cipher>", "cipher suite to use"],
+ ["--hash <hash>", "hashing function to use"],
+ ["--iterations <iterations>", "number of iteration to apply"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Decrypts an encrypted configuration value"
+
+ #
+ # Implementation
+ #
+
+ defp supports_cipher(cipher), do: Enum.member?(:rabbit_pbe.supported_ciphers(), cipher)
+
+ defp supports_hash(hash), do: Enum.member?(:rabbit_pbe.supported_hashes(), hash)
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_queue_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_queue_command.ex
new file mode 100644
index 0000000000..5e5fe9b9c0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_queue_command.ex
@@ -0,0 +1,125 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [if_empty: :boolean, if_unused: :boolean, timeout: :integer]
+ def aliases(), do: [e: :if_empty, u: :if_unused, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {
+ args,
+ Map.merge(%{if_empty: false, if_unused: false, vhost: "/"}, opts)
+ }
+ end
+
+ def validate([], _opts) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _opts) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([""], _opts) do
+ {
+ :validation_failure,
+ {:bad_argument, "queue name cannot be an empty string"}
+ }
+ end
+
+ def validate([_], _opts) do
+ :ok
+ end
+
+ ## Validate rabbit application is running
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([qname], %{
+ node: node,
+ vhost: vhost,
+ if_empty: if_empty,
+ if_unused: if_unused,
+ timeout: timeout
+ }) do
+ ## Generate queue resource name from queue name and vhost
+ queue_resource = :rabbit_misc.r(vhost, :queue, qname)
+ ## Lookup a queue on broker node using resource name
+ case :rabbit_misc.rpc_call(node, :rabbit_amqqueue, :lookup, [queue_resource]) do
+ {:ok, queue} ->
+ ## Delete queue
+ :rabbit_misc.rpc_call(
+ node,
+ :rabbit_amqqueue,
+ :delete,
+ [queue, if_unused, if_empty, "cli_user"],
+ timeout
+ )
+
+ {:error, _} = error ->
+ error
+ end
+ end
+
+ def output({:error, :not_found}, _options) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue not found"}
+ end
+
+ def output({:error, :not_empty}, _options) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue is not empty"}
+ end
+
+ def output({:error, :in_use}, _options) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue is in use"}
+ end
+
+ def output({:ok, qlen}, _options) do
+ {:ok, "Queue was successfully deleted with #{qlen} messages"}
+ end
+
+ ## Use default output for all non-special case outputs
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([qname], %{vhost: vhost, if_empty: if_empty, if_unused: if_unused}) do
+ if_empty_str =
+ case if_empty do
+ true -> ["if queue is empty "]
+ false -> []
+ end
+
+ if_unused_str =
+ case if_unused do
+ true -> ["if queue is unused "]
+ false -> []
+ end
+
+ "Deleting queue '#{qname}' on vhost '#{vhost}' " <>
+ Enum.join(Enum.concat([if_empty_str, if_unused_str]), "and ") <> "..."
+ end
+
+ def usage(), do: "delete_queue <queue_name> [--if-empty|-e] [--if-unused|-u]"
+
+ def usage_additional() do
+ [
+ ["<queue_name>", "name of the queue to delete"],
+ ["--if-empty", "delete the queue if it is empty (has no messages ready for delivery)"],
+ ["--if-unused", "delete the queue only if it has no consumers"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.queues()
+ ]
+ end
+
+ def help_section(), do: :queues
+
+ def description(), do: "Deletes a queue"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_user_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_user_command.ex
new file mode 100644
index 0000000000..84f00a96f4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_user_command.ex
@@ -0,0 +1,52 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.DeleteUserCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :delete_user,
+ [username, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exists"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User \"#{username}\" does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "delete_user <username>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Name of the user to delete."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Removes a user from the internal database. Has no effect on users provided by external backends such as LDAP"
+
+ def banner([arg], _), do: "Deleting user \"#{arg}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_vhost_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_vhost_command.ex
new file mode 100644
index 0000000000..8ff6e1f047
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_vhost_command.ex
@@ -0,0 +1,41 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.DeleteVhostCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([arg], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost, :delete, [arg, Helpers.cli_acting_user()])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "delete_vhost <vhost>"
+
+ def usage_additional() do
+ [
+ ["<vhost>", "Name of the virtual host to delete."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Deletes a virtual host"
+
+ def banner([arg], _), do: "Deleting vhost \"#{arg}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/enable_feature_flag_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/enable_feature_flag_command.ex
new file mode 100644
index 0000000000..6af5a79e49
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/enable_feature_flag_command.ex
@@ -0,0 +1,60 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2018-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+
+defmodule RabbitMQ.CLI.Ctl.Commands.EnableFeatureFlagCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts), do: {args, opts}
+
+ def validate([], _), do: {:validation_failure, :not_enough_args}
+ def validate([_|_] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
+ def validate([""], _), do: {:validation_failure, {:bad_argument, "feature_flag cannot be an empty string."}}
+ def validate([_], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run(["all"], %{node: node_name}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable_all, []) do
+ # Server does not support feature flags, consider none are available.
+ # See rabbitmq/rabbitmq-cli#344 for context. MK.
+ {:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
+ {:badrpc, _} = err -> err
+ other -> other
+ end
+ end
+
+ def run([feature_flag], %{node: node_name}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable, [String.to_atom(feature_flag)]) do
+ # Server does not support feature flags, consider none are available.
+ # See rabbitmq/rabbitmq-cli#344 for context. MK.
+ {:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
+ {:badrpc, _} = err -> err
+ other -> other
+ end
+ end
+
+ def output({:error, :unsupported}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "This feature flag is not supported by node #{node_name}"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "enable_feature_flag <all | feature_flag>"
+
+ def usage_additional() do
+ [
+ ["<feature_flag>", "name of the feature flag to enable, or \"all\" to enable all supported flags"]
+ ]
+end
+
+ def help_section(), do: :feature_flags
+
+ def description(), do: "Enables a feature flag or all supported feature flags on the target node"
+
+ def banner(["all"], _), do: "Enabling all feature flags ..."
+
+ def banner([feature_flag], _), do: "Enabling feature flag \"#{feature_flag}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex
new file mode 100644
index 0000000000..c625b4a5f5
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex
@@ -0,0 +1,102 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def switches() do
+ [
+ cipher: :atom,
+ hash: :atom,
+ iterations: :integer
+ ]
+ end
+
+ def distribution(_), do: :none
+
+ def merge_defaults(args, opts) do
+ with_defaults = Map.merge(%{
+ cipher: :rabbit_pbe.default_cipher(),
+ hash: :rabbit_pbe.default_hash(),
+ iterations: :rabbit_pbe.default_iterations()
+ }, opts)
+ {args, with_defaults}
+ end
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, {:not_enough_args, "Please provide a value to decode and a passphrase."}}
+ end
+
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(args, opts) when length(args) === 2 do
+ case {supports_cipher(opts.cipher), supports_hash(opts.hash), opts.iterations > 0} do
+ {false, _, _} ->
+ {:validation_failure, {:bad_argument, "The requested cipher is not supported."}}
+
+ {_, false, _} ->
+ {:validation_failure, {:bad_argument, "The requested hash is not supported"}}
+
+ {_, _, false} ->
+ {:validation_failure, {:bad_argument, "The requested number of iterations is incorrect"}}
+
+ {true, true, true} ->
+ :ok
+ end
+ end
+
+ def run([value, passphrase], %{cipher: cipher, hash: hash, iterations: iterations}) do
+ try do
+ term_value = Helpers.evaluate_input_as_term(value)
+ result = {:encrypted, _} = :rabbit_pbe.encrypt_term(cipher, hash, iterations, passphrase, term_value)
+ {:ok, result}
+ catch
+ _, _ ->
+ {:error, "Error during cipher operation."}
+ end
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def banner([_, _], _) do
+ "Encrypting value ..."
+ end
+
+ def usage, do: "encode value passphrase [--cipher <cipher>] [--hash <hash>] [--iterations <iterations>]"
+
+ def usage_additional() do
+ [
+ ["<value>", "config value to encode"],
+ ["<passphrase>", "passphrase to use with the config value encryption key"],
+ ["--cipher <cipher>", "cipher suite to use"],
+ ["--hash <hash>", "hashing function to use"],
+ ["--iterations <iterations>", "number of iteration to apply"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Encrypts a sensitive configuration value"
+
+ #
+ # Implementation
+ #
+
+ defp supports_cipher(cipher), do: Enum.member?(:rabbit_pbe.supported_ciphers(), cipher)
+
+ defp supports_hash(hash), do: Enum.member?(:rabbit_pbe.supported_hashes(), hash)
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/environment_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/environment_command.ex
new file mode 100644
index 0000000000..ac807512a9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/environment_command.ex
@@ -0,0 +1,38 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.EnvironmentCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :environment, [])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "environment"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Displays the name and value of each variable in the application environment for each running application"
+
+ def banner(_, %{node: node_name}), do: "Application environment of node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_command.ex
new file mode 100644
index 0000000000..35fe0a8803
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_command.ex
@@ -0,0 +1,107 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.EvalCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ErlEval, ExitCodes, Input}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _) do
+ # value will be provided via standard input
+ :ok
+ end
+
+ def validate(["" | _], _) do
+ {:validation_failure, "Expression must not be blank"}
+ end
+
+ def validate([expr | _], _) do
+ case ErlEval.parse_expr(expr) do
+ {:ok, _} -> :ok
+ {:error, err} -> {:validation_failure, err}
+ end
+ end
+
+ def run([], %{node: node_name} = opts) do
+ case Input.consume_multiline_string() do
+ :eof -> {:error, :not_enough_args}
+ expr ->
+ case ErlEval.parse_expr(expr) do
+ {:ok, parsed} ->
+ bindings = make_bindings([], opts)
+
+ case :rabbit_misc.rpc_call(node_name, :erl_eval, :exprs, [parsed, bindings]) do
+ {:value, value, _} -> {:ok, value}
+ err -> err
+ end
+
+ {:error, msg} -> {:error, msg}
+ end
+ end
+ end
+ def run([expr | arguments], %{node: node_name} = opts) do
+ case ErlEval.parse_expr(expr) do
+ {:ok, parsed} ->
+ bindings = make_bindings(arguments, opts)
+
+ case :rabbit_misc.rpc_call(node_name, :erl_eval, :exprs, [parsed, bindings]) do
+ {:value, value, _} -> {:ok, value}
+ err -> err
+ end
+
+ {:error, msg} -> {:error, msg}
+ end
+ end
+
+ def output({:error, :not_enough_args}, _) do
+ {:error, ExitCodes.exit_dataerr(), "Expression to evaluate is not provided via argument or stdin"}
+ end
+ def output({:error, msg}, _) do
+ {:error, ExitCodes.exit_dataerr(), "Evaluation failed: #{msg}"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "eval <expression>"
+
+ def usage_additional() do
+ [
+ ["<expression>", "Expression to evaluate"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.cli(),
+ DocGuide.monitoring(),
+ DocGuide.troubleshooting()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Evaluates a snippet of Erlang code on the target node"
+
+ def banner(_, _), do: nil
+
+ #
+ # Implementation
+ #
+
+ defp make_bindings(args, opts) do
+ Enum.with_index(args, 1)
+ |> Enum.map(fn {val, index} -> {String.to_atom("_#{index}"), val} end)
+ |> Enum.concat(option_bindings(opts))
+ end
+
+ defp option_bindings(opts) do
+ Enum.to_list(opts)
+ |> Enum.map(fn {key, val} -> {String.to_atom("_#{key}"), val} end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_file_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_file_command.ex
new file mode 100644
index 0000000000..6f46abbf17
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/eval_file_command.ex
@@ -0,0 +1,75 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.EvalFileCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ErlEval, ExitCodes}
+ alias RabbitMQ.CLI.Ctl.Commands.EvalCommand
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(["" | _], _) do
+ {:validation_failure, "File path must not be blank"}
+ end
+
+ def validate([file_path], _) do
+ case File.read(file_path) do
+ {:ok, expr} ->
+ case ErlEval.parse_expr(expr) do
+ {:ok, _} -> :ok
+ {:error, err} -> {:validation_failure, err}
+ end
+ {:error, :enoent} ->
+ {:validation_failure, "File #{file_path} does not exist"}
+ {:error, :eacces} ->
+ {:validation_failure, "Insufficient permissions to read file #{file_path}"}
+ {:error, err} ->
+ {:validation_failure, err}
+ end
+ end
+
+ def run([file_path], opts) do
+ case File.read(file_path) do
+ {:ok, expr} -> EvalCommand.run([expr], opts)
+ {:error, err} -> {:error, err}
+ end
+
+ end
+
+ def output({:error, msg}, _) do
+ {:error, ExitCodes.exit_dataerr(), "Evaluation failed: #{msg}"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "eval_file <file path>"
+
+ def usage_additional() do
+ [
+ ["<file path>", "Path to the file to evaluate"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.cli(),
+ DocGuide.monitoring(),
+ DocGuide.troubleshooting()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Evaluates a file that contains a snippet of Erlang code on the target node"
+
+ def banner(_, _), do: nil
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/exec_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/exec_command.ex
new file mode 100644
index 0000000000..469047c1af
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/exec_command.ex
@@ -0,0 +1,80 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ExecCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def switches(), do: [offline: :boolean]
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{}), do: :cli
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([""], _) do
+ {:validation_failure, "Expression must not be blank"}
+ end
+
+ def validate([string], _) do
+ try do
+ Code.compile_string(string)
+ :ok
+ rescue
+ ex in SyntaxError ->
+ {:validation_failure, "SyntaxError: " <> Exception.message(ex)}
+
+ _ ->
+ :ok
+ end
+ end
+
+ def run([expr], %{} = opts) do
+ try do
+ {val, _} = Code.eval_string(expr, [options: opts], __ENV__)
+ {:ok, val}
+ rescue
+ ex ->
+ {:error, Exception.message(ex)}
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Inspect
+
+ def banner(_, _), do: nil
+
+ def usage, do: "exec <expression> [--offline]"
+
+ def usage_additional() do
+ [
+ ["<expression>", "Expression to evaluate"],
+ ["--offline", "disable inter-node communication"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.cli(),
+ DocGuide.monitoring(),
+ DocGuide.troubleshooting()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Evaluates a snippet of Elixir code on the CLI node"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/export_definitions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/export_definitions_command.ex
new file mode 100644
index 0000000000..e4b026f160
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/export_definitions_command.ex
@@ -0,0 +1,143 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ExportDefinitionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(["-"] = args, opts) do
+ {args, Map.merge(%{format: "json", silent: true}, Helpers.case_insensitive_format(opts))}
+ end
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{format: "json"}, Helpers.case_insensitive_format(opts))}
+ end
+
+ def switches(), do: [timeout: :integer, format: :string]
+ def aliases(), do: [t: :timeout]
+
+ def validate(_, %{format: format})
+ when format != "json" and format != "JSON" and format != "erlang" do
+ {:validation_failure, {:bad_argument, "Format should be either json or erlang"}}
+ end
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+ # output to stdout
+ def validate(["-"], _) do
+ :ok
+ end
+ def validate([path], _) do
+ dir = Path.dirname(path)
+ case File.exists?(dir, [raw: true]) do
+ true -> :ok
+ false -> {:validation_failure, {:bad_argument, "Directory #{dir} does not exist"}}
+ end
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run(["-"], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :all_definitions, [], timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ result -> {:ok, result}
+ end
+ end
+ def run([path], %{node: node_name, timeout: timeout, format: format}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :all_definitions, [], timeout) do
+ {:badrpc, _} = err -> err
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ result ->
+ # write to the file in run/2 because output/2 is not meant to
+ # produce side effects
+ body = serialise(result, format)
+ abs_path = Path.absname(path)
+
+ File.rm(abs_path)
+ case File.write(abs_path, body) do
+ # no output
+ :ok -> {:ok, nil}
+ {:error, :enoent} ->
+ {:error, ExitCodes.exit_dataerr(), "Parent directory or file #{path} does not exist"}
+ {:error, :enotdir} ->
+ {:error, ExitCodes.exit_dataerr(), "Parent directory of file #{path} is not a directory"}
+ {:error, :enospc} ->
+ {:error, ExitCodes.exit_dataerr(), "No space left on device hosting #{path}"}
+ {:error, :eacces} ->
+ {:error, ExitCodes.exit_dataerr(), "No permissions to write to file #{path} or its parent directory"}
+ {:error, :eisdir} ->
+ {:error, ExitCodes.exit_dataerr(), "Path #{path} is a directory"}
+ {:error, err} ->
+ {:error, ExitCodes.exit_dataerr(), "Could not write to file #{path}: #{err}"}
+ end
+ end
+ end
+
+ def output({:ok, nil}, _) do
+ {:ok, nil}
+ end
+ def output({:ok, result}, %{format: "json"}) when is_map(result) do
+ {:ok, serialise(result, "json")}
+ end
+ def output({:ok, result}, %{format: "erlang"}) when is_map(result) do
+ {:ok, serialise(result, "erlang")}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def printer(), do: RabbitMQ.CLI.Printers.StdIORaw
+
+ def usage, do: "export_definitions <file_path | \"-\"> [--format <json | erlang>]"
+
+ def usage_additional() do
+ [
+ ["<file>", "Local file path to export to. Pass a dash (-) for stdout."],
+ ["--format", "output format to use: json or erlang"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.definitions()
+ ]
+ end
+
+ def help_section(), do: :definitions
+
+ def description(), do: "Exports definitions in JSON or compressed Erlang Term Format."
+
+ def banner([path], %{format: fmt}), do: "Exporting definitions in #{human_friendly_format(fmt)} to a file at \"#{path}\" ..."
+
+ #
+ # Implementation
+ #
+
+ defp serialise(raw_map, "json") do
+ # make sure all runtime parameter values are maps, otherwise
+ # they will end up being a list of pairs (a keyword list/proplist)
+ # in the resulting JSON document
+ map = Map.update!(raw_map, :parameters, fn(params) ->
+ Enum.map(params, fn(param) ->
+ Map.update!(param, "value", &:rabbit_data_coercion.to_map/1)
+ end)
+ end)
+ {:ok, json} = JSON.encode(map)
+ json
+ end
+
+ defp serialise(map, "erlang") do
+ :erlang.term_to_binary(map, [{:compressed, 9}])
+ end
+
+ defp human_friendly_format("JSON"), do: "JSON"
+ defp human_friendly_format("json"), do: "JSON"
+ defp human_friendly_format("erlang"), do: "Erlang term format"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_boot_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_boot_command.ex
new file mode 100644
index 0000000000..261f86c6c1
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_boot_command.ex
@@ -0,0 +1,57 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ForceBootCommand do
+ alias RabbitMQ.CLI.Core.{Config, DocGuide}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ ##
+ def validate_execution_environment(args, opts) do
+ ## We don't use RequiresRabbitAppStopped helper because we don't want to fail
+ ## the validation if the node is not running.
+ case RabbitMQ.CLI.Core.Validators.rabbit_is_not_running(args, opts) do
+ :ok -> :ok
+ {:validation_failure, _} = failure -> failure
+ _other -> RabbitMQ.CLI.Core.Validators.node_is_not_running(args, opts)
+ end
+ end
+
+ def run([], %{node: node_name} = opts) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :force_load_next_boot, []) do
+ {:badrpc, :nodedown} ->
+ case Config.get_option(:mnesia_dir, opts) do
+ nil ->
+ {:error, :mnesia_dir_not_found}
+
+ dir ->
+ File.write(Path.join(dir, "force_load"), "")
+ end
+
+ _ ->
+ :ok
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "force_boot"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Forces node to start even if it cannot contact or rejoin any of its previously known peers"
+
+ def banner(_, _), do: nil
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_gc_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_gc_command.ex
new file mode 100644
index 0000000000..975154be50
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_gc_command.ex
@@ -0,0 +1,35 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ForceGcCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_runtime, :gc_all_processes, [], timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "force_gc"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.memory_use()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description, do: "Makes all Erlang processes on the target node perform/schedule a full sweep garbage collection"
+
+ def banner([], %{node: node_name}), do: "Will ask all processes on node #{node_name} to schedule a full sweep GC"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_reset_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_reset_command.ex
new file mode 100644
index 0000000000..5f202f9d08
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/force_reset_command.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ForceResetCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppStopped
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :force_reset, [])
+ end
+
+ def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
+ {:error, ExitCodes.exit_software(),
+ RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "force_reset"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Forcefully returns a RabbitMQ node to its virgin state"
+
+ def banner(_, %{node: node_name}), do: "Forcefully resetting node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/forget_cluster_node_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/forget_cluster_node_command.ex
new file mode 100644
index 0000000000..cdf5ae7fbe
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/forget_cluster_node_command.ex
@@ -0,0 +1,122 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ForgetClusterNodeCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Distribution, Validators}
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [offline: :boolean]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{offline: false}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+
+ def validate_execution_environment([_node_to_remove] = args, %{offline: true} = opts) do
+ Validators.chain(
+ [
+ &Validators.node_is_not_running/2,
+ &Validators.mnesia_dir_is_set/2,
+ &Validators.feature_flags_file_is_set/2,
+ &Validators.rabbit_is_loaded/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def validate_execution_environment([_], %{offline: false}) do
+ :ok
+ end
+
+ def run([node_to_remove], %{node: node_name, offline: true} = opts) do
+ Stream.concat([
+ become(node_name, opts),
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ :rabbit_event.start_link()
+ :rabbit_mnesia.forget_cluster_node(to_atom(node_to_remove), true)
+ end)
+ ])
+ end
+
+ def run([node_to_remove], %{node: node_name, offline: false}) do
+ atom_name = to_atom(node_to_remove)
+ args = [atom_name, false]
+ case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :forget_cluster_node, args) do
+ {:error, {:failed_to_remove_node, ^atom_name, {:active, _, _}}} ->
+ {:error, "RabbitMQ on node #{node_to_remove} must be stopped with 'rabbitmqctl -n #{node_to_remove} stop_app' before it can be removed"};
+ {:error, _} = error -> error;
+ {:badrpc, _} = error -> error;
+ :ok ->
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :shrink_all, [atom_name]) do
+ {:error, _} ->
+ {:error, "RabbitMQ failed to shrink some of the quorum queues on node #{node_to_remove}"};
+ _ -> :ok
+ end
+ other -> other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "forget_cluster_node [--offline] <existing_cluster_member_node>"
+ end
+
+ def usage_additional() do
+ [
+ ["--offline", "try to update cluster membership state directly. Use when target node is stopped. Only works for local nodes."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering(),
+ DocGuide.cluster_formation()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Removes a node from the cluster"
+
+ def banner([node_to_remove], %{offline: true}) do
+ "Removing node #{node_to_remove} from the cluster. Warning: quorum queues cannot be shrunk in offline mode"
+ end
+ def banner([node_to_remove], _) do
+ "Removing node #{node_to_remove} from the cluster"
+ end
+
+ #
+ # Implementation
+ #
+
+ defp become(node_name, opts) do
+ :error_logger.tty(false)
+
+ case :net_adm.ping(node_name) do
+ :pong ->
+ exit({:node_running, node_name})
+
+ :pang ->
+ :ok = :net_kernel.stop()
+
+ Stream.concat([
+ [" * Impersonating node: #{node_name}..."],
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ {:ok, _} = Distribution.start_as(node_name, opts)
+ " done"
+ end),
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ dir = :mnesia.system_info(:directory)
+ " * Mnesia directory: #{dir}..."
+ end)
+ ])
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex
new file mode 100644
index 0000000000..8f459cc83f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex
@@ -0,0 +1,343 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+alias RabbitMQ.CLI.CommandBehaviour
+
+defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do
+ alias RabbitMQ.CLI.Core.{CommandModules, Config, ExitCodes}
+ alias RabbitMQ.CLI.Core.CommandModules
+
+ import RabbitMQ.CLI.Core.ANSI
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics, :plugins, :queues, :upgrade]
+ def switches(), do: [list_commands: :boolean]
+
+ def distribution(_), do: :none
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _), do: :ok
+ def validate([_command], _), do: :ok
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def run([command_name | _], opts) do
+ CommandModules.load(opts)
+
+ module_map = CommandModules.module_map()
+ case module_map[command_name] do
+ nil ->
+ # command not found
+ # {:error, all_usage(opts)}
+ case RabbitMQ.CLI.AutoComplete.suggest_command(command_name, module_map) do
+ {:suggest, suggested} ->
+ suggest_message = "\nCommand '#{command_name}' not found. \n" <>
+ "Did you mean '#{suggested}'? \n"
+ {:error, ExitCodes.exit_usage(), suggest_message}
+ nil ->
+ {:error, ExitCodes.exit_usage(), "\nCommand '#{command_name}' not found."}
+ end
+
+ command ->
+ {:ok, command_usage(command, opts)}
+ end
+ end
+
+ def run([], opts) do
+ CommandModules.load(opts)
+
+ case opts[:list_commands] do
+ true ->
+ {:ok, commands_description()}
+ _ ->
+ {:ok, all_usage(opts)}
+ end
+ end
+
+ def output({:ok, result}, _) do
+ {:ok, result}
+ end
+ def output({:error, result}, _) do
+ {:error, ExitCodes.exit_usage(), result}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner(_, _), do: nil
+
+ def help_section(), do: :help
+
+ def description(), do: "Displays usage information for a command"
+
+ def usage(), do: "help (<command> | [--list-commands])"
+
+ def usage_additional() do
+ [
+ ["--list-commands", "only output a list of discovered commands"]
+ ]
+ end
+
+
+ #
+ # Implementation
+ #
+
+ def all_usage(opts) do
+ tool_name = program_name(opts)
+ tool_usage(tool_name) ++
+ ["\n\nAvailable commands:\n"] ++ commands_description() ++
+ help_footer(tool_name)
+ end
+
+ def command_usage(command, opts) do
+ Enum.join([base_usage(command, opts)] ++
+ command_description(command) ++
+ additional_usage(command) ++
+ relevant_doc_guides(command) ++
+ general_options_usage(),
+ "\n\n") <> "\n"
+ end
+
+ defp tool_usage(tool_name) do
+ [
+ "\n#{bright("Usage")}\n\n" <>
+ "#{tool_name} [--node <node>] [--timeout <timeout>] [--longnames] [--quiet] <command> [<command options>]"
+ ]
+ end
+
+ def base_usage(command, opts) do
+ tool_name = program_name(opts)
+
+ maybe_timeout =
+ case command_supports_timeout(command) do
+ true -> " [--timeout <timeout>]"
+ false -> ""
+ end
+
+ Enum.join([
+ "\n#{bright("Usage")}\n\n",
+ "#{tool_name} [--node <node>] [--longnames] [--quiet] " <>
+ flatten_string(command.usage(), maybe_timeout)
+ ])
+ end
+
+ defp flatten_string(list, additional) when is_list(list) do
+ list
+ |> Enum.map(fn line -> line <> additional end)
+ |> Enum.join("\n")
+ end
+
+ defp flatten_string(str, additional) when is_binary(str) do
+ str <> additional
+ end
+
+ defp general_options_usage() do
+ [
+ "#{bright("General Options")}
+
+The following options are accepted by most or all commands.
+
+short | long | description
+-----------------|---------------|--------------------------------
+-? | --help | displays command help
+-n <node> | --node <node> | connect to node <node>
+-l | --longnames | use long host names
+-t | --timeout <n> | for commands that support it, operation timeout in seconds
+-q | --quiet | suppress informational messages
+-s | --silent | suppress informational messages
+ | and table header row
+-p | --vhost | for commands that are scoped to a virtual host,
+ | | virtual host to use
+ | --formatter | alternative result formatter to use
+ | if supported: json, pretty_table, table, csv, erlang
+ not all commands support all (or any) alternative formatters."]
+ end
+
+ defp command_description(command) do
+ case CommandBehaviour.description(command) do
+ "" -> []
+ other -> [other <> ".\n"]
+ end
+ end
+
+ defp list_item_formatter([option, description]) do
+ "#{option}\n\t#{description}\n"
+ end
+ defp list_item_formatter({option, description}) do
+ "#{option}\n\t#{description}\n"
+ end
+ defp list_item_formatter(line) do
+ "#{line}\n"
+ end
+
+ defp additional_usage(command) do
+ command_usage =
+ case CommandBehaviour.usage_additional(command) do
+ list when is_list(list) -> list |> Enum.map(&list_item_formatter/1)
+ bin when is_binary(bin) -> ["#{bin}\n"]
+ end
+ case command_usage do
+ [] -> []
+ usage ->
+ [flatten_string(["#{bright("Arguments and Options")}\n" | usage], "")]
+ end
+ end
+
+ defp relevant_doc_guides(command) do
+ guide_list =
+ case CommandBehaviour.usage_doc_guides(command) do
+ list when is_list(list) -> list |> Enum.map(fn ln -> " * #{ln}\n" end)
+ bin when is_binary(bin) -> [" * #{bin}\n"]
+ end
+ case guide_list do
+ [] -> []
+ usage ->
+ [flatten_string(["#{bright("Relevant Doc Guides")}\n" | usage], "")]
+ end
+ end
+
+ defp help_footer(tool_name) do
+ ["Use '#{tool_name} help <command>' to learn more about a specific command"]
+ end
+
+ defp command_supports_timeout(command) do
+ nil != CommandBehaviour.switches(command)[:timeout]
+ end
+
+ defp commands_description() do
+ module_map = CommandModules.module_map()
+
+ pad_commands_to = Enum.reduce(module_map, 0,
+ fn({name, _}, longest) ->
+ name_length = String.length(name)
+ case name_length > longest do
+ true -> name_length
+ false -> longest
+ end
+ end)
+
+ lines = module_map
+ |> Enum.map(
+ fn({name, cmd}) ->
+ description = CommandBehaviour.description(cmd)
+ help_section = CommandBehaviour.help_section(cmd)
+ {name, {description, help_section}}
+ end)
+ |> Enum.group_by(fn({_, {_, help_section}}) -> help_section end)
+ |> Enum.sort_by(
+ fn({help_section, _}) ->
+ case help_section do
+ :deprecated -> 999
+ :other -> 100
+ {:plugin, _} -> 99
+ :help -> 1
+ :node_management -> 2
+ :cluster_management -> 3
+ :replication -> 3
+ :user_management -> 4
+ :access_control -> 5
+ :observability_and_health_checks -> 6
+ :parameters -> 7
+ :policies -> 8
+ :virtual_hosts -> 9
+ _ -> 98
+ end
+ end)
+ |> Enum.map(
+ fn({help_section, section_helps}) ->
+ [
+ "\n" <> bright(section_head(help_section)) <> ":\n\n" |
+ Enum.sort(section_helps)
+ |> Enum.map(
+ fn({name, {description, _}}) ->
+ " #{String.pad_trailing(name, pad_commands_to)} #{description}\n"
+ end)
+ ]
+
+ end)
+ |> Enum.concat()
+
+ lines ++ ["\n"]
+ end
+
+ defp section_head(help_section) do
+ case help_section do
+ :help ->
+ "Help"
+ :user_management ->
+ "Users"
+ :cluster_management ->
+ "Cluster"
+ :replication ->
+ "Replication"
+ :node_management ->
+ "Nodes"
+ :queues ->
+ "Queues"
+ :observability_and_health_checks ->
+ "Monitoring, observability and health checks"
+ :virtual_hosts ->
+ "Virtual hosts"
+ :access_control ->
+ "Access Control"
+ :parameters ->
+ "Parameters"
+ :policies ->
+ "Policies"
+ :configuration ->
+ "Configuration and Environment"
+ :feature_flags ->
+ "Feature flags"
+ :other ->
+ "Other"
+ {:plugin, plugin} ->
+ plugin_section(plugin) <> " plugin"
+ custom ->
+ snake_case_to_capitalized_string(custom)
+ end
+ end
+
+ defp strip_rabbitmq_prefix(value, regex) do
+ Regex.replace(regex, value, "")
+ end
+
+ defp format_known_plugin_name_fragments(value) do
+ case value do
+ ["amqp1.0"] -> "AMQP 1.0"
+ ["amqp1", "0"] -> "AMQP 1.0"
+ ["management"] -> "Management"
+ ["management", "agent"] -> "Management"
+ ["mqtt"] -> "MQTT"
+ ["stomp"] -> "STOMP"
+ ["web", "mqtt"] -> "Web MQTT"
+ ["web", "stomp"] -> "Web STOMP"
+ [other] -> snake_case_to_capitalized_string(other)
+ fragments -> snake_case_to_capitalized_string(Enum.join(fragments, "_"))
+ end
+ end
+
+ defp plugin_section(plugin) do
+ regex = Regex.recompile!(~r/^rabbitmq_/)
+
+ to_string(plugin)
+ # drop rabbitmq_
+ |> strip_rabbitmq_prefix(regex)
+ |> String.split("_")
+ |> format_known_plugin_name_fragments()
+ end
+
+ defp snake_case_to_capitalized_string(value) do
+ to_string(value)
+ |> String.split("_")
+ |> Enum.map(&String.capitalize/1)
+ |> Enum.join(" ")
+ end
+
+ defp program_name(opts) do
+ Config.get_option(:script_name, opts)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex
new file mode 100644
index 0000000000..13f3468cb6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/hipe_compile_command.ex
@@ -0,0 +1,98 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.HipeCompileCommand do
+ @moduledoc """
+ HiPE support has been deprecated since Erlang/OTP 22 (mid-2019) and
+ won't be a part of Erlang/OTP 24.
+
+ Therefore this command is DEPRECATED and is no-op.
+ """
+
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ import RabbitMQ.CLI.Core.CodePath
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ #
+ # API
+ #
+
+ def distribution(_), do: :none
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _), do: {:validation_failure, :not_enough_args}
+
+ def validate([target_dir], opts) do
+ :ok
+ |> Validators.validate_step(fn ->
+ case acceptable_path?(target_dir) do
+ true -> :ok
+ false -> {:error, {:bad_argument, "Target directory path cannot be blank"}}
+ end
+ end)
+ |> Validators.validate_step(fn ->
+ case File.dir?(target_dir) do
+ true ->
+ :ok
+
+ false ->
+ case File.mkdir_p(target_dir) do
+ :ok ->
+ :ok
+
+ {:error, perm} when perm == :eperm or perm == :eacces ->
+ {:error,
+ {:bad_argument,
+ "Cannot create target directory #{target_dir}: insufficient permissions"}}
+ end
+ end
+ end)
+ |> Validators.validate_step(fn -> require_rabbit(opts) end)
+ end
+
+ def validate(_, _), do: {:validation_failure, :too_many_args}
+
+ def run([_target_dir], _opts) do
+ :ok
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "hipe_compile <directory>"
+
+ def usage_additional do
+ [
+ ["<directory>", "Target directory for HiPE-compiled modules"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.erlang_versions()
+ ]
+ end
+
+ def help_section(), do: :deprecated
+
+ def description() do
+ "DEPRECATED. This command is a no-op. HiPE is no longer supported by modern Erlang versions"
+ end
+
+ def banner([_target_dir], _) do
+ "This command is a no-op. HiPE is no longer supported by modern Erlang versions"
+ end
+
+ #
+ # Implementation
+ #
+
+ # Accepts any non-blank path
+ defp acceptable_path?(value) do
+ String.length(String.trim(value)) != 0
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/import_definitions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/import_definitions_command.ex
new file mode 100644
index 0000000000..45ca0074f3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/import_definitions_command.ex
@@ -0,0 +1,136 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ImportDefinitionsCommand do
+ alias RabbitMQ.CLI.Core.{Config, DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(["-"] = args, opts) do
+ {args, Map.merge(%{format: "json", silent: true}, Helpers.case_insensitive_format(opts))}
+ end
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{format: "json"}, Helpers.case_insensitive_format(opts))}
+ end
+
+ def switches(), do: [timeout: :integer, format: :string]
+ def aliases(), do: [t: :timeout]
+
+ def validate(_, %{format: format})
+ when format != "json" and format != "JSON" and format != "erlang" do
+ {:validation_failure, {:bad_argument, "Format should be either json or erlang"}}
+ end
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate([path], _) do
+ case File.exists?(path, [raw: true]) do
+ true -> :ok
+ false -> {:validation_failure, {:bad_argument, "File #{path} does not exist"}}
+ end
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, format: format, timeout: timeout}) do
+ case IO.read(:stdio, :all) do
+ :eof -> {:error, :not_enough_args}
+ bin ->
+ case deserialise(bin, format) do
+ {:error, error} ->
+ {:error, ExitCodes.exit_dataerr(), "Failed to deserialise input (format: #{human_friendly_format(format)}) (error: #{inspect(error)})"}
+ {:ok, map} ->
+ :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :import_parsed, [map], timeout)
+ end
+ end
+ end
+ def run([path], %{node: node_name, timeout: timeout, format: format}) do
+ abs_path = Path.absname(path)
+
+ case File.read(abs_path) do
+ {:ok, ""} ->
+ {:error, ExitCodes.exit_dataerr(), "File #{path} is zero-sized"}
+ {:ok, bin} ->
+ case deserialise(bin, format) do
+ {:error, error} ->
+ {:error, ExitCodes.exit_dataerr(), "Failed to deserialise input (format: #{human_friendly_format(format)}) (error: #{inspect(error)})"}
+ {:ok, map} ->
+ :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :import_parsed, [map], timeout)
+ end
+ {:error, :enoent} ->
+ {:error, ExitCodes.exit_dataerr(), "Parent directory or file #{path} does not exist"}
+ {:error, :enotdir} ->
+ {:error, ExitCodes.exit_dataerr(), "Parent directory of file #{path} is not a directory"}
+ {:error, :eacces} ->
+ {:error, ExitCodes.exit_dataerr(), "No permissions to read from file #{path} or its parent directory"}
+ {:error, :eisdir} ->
+ {:error, ExitCodes.exit_dataerr(), "Path #{path} is a directory"}
+ {:error, err} ->
+ {:error, ExitCodes.exit_dataerr(), "Could not read from file #{path}: #{err}"}
+ end
+ end
+
+ def output(:ok, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name}}
+ end
+ def output(:ok, opts) do
+ case Config.output_less?(opts) do
+ true -> :ok
+ false -> {:ok, "Successfully started definition import. " <>
+ "This process is asynchronous and can take some time.\n"}
+ end
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def printer(), do: RabbitMQ.CLI.Printers.StdIORaw
+
+ def usage, do: "import_definitions <file_path | \"-\"> [--format <json | erlang>]"
+
+ def usage_additional() do
+ [
+ ["[file]", "Local file path to import from. If omitted will be read from standard input."],
+ ["--format", "input format to use: json or erlang"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.definitions()
+ ]
+ end
+
+ def help_section(), do: :definitions
+
+ def description(), do: "Imports definitions in JSON or compressed Erlang Term Format."
+
+ def banner([], %{format: fmt}) do
+ "Importing definitions in #{human_friendly_format(fmt)} from standard input ..."
+ end
+ def banner([path], %{format: fmt}) do
+ "Importing definitions in #{human_friendly_format(fmt)} from a file at \"#{path}\" ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp deserialise(bin, "json") do
+ JSON.decode(bin)
+ end
+
+ defp deserialise(bin, "erlang") do
+ try do
+ {:ok, :erlang.binary_to_term(bin)}
+ rescue e in ArgumentError ->
+ {:error, e.message}
+ end
+ end
+
+ defp human_friendly_format("JSON"), do: "JSON"
+ defp human_friendly_format("json"), do: "JSON"
+ defp human_friendly_format("erlang"), do: "Erlang term format"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/join_cluster_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/join_cluster_command.ex
new file mode 100644
index 0000000000..765fbd43f1
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/join_cluster_command.ex
@@ -0,0 +1,95 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.JoinClusterCommand do
+ alias RabbitMQ.CLI.Core.{Config, DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches() do
+ [
+ disc: :boolean,
+ ram: :boolean
+ ]
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{disc: false, ram: false}, opts)}
+ end
+
+ def validate(_, %{disc: true, ram: true}) do
+ {:validation_failure, {:bad_argument, "The node type must be either disc or ram."}}
+ end
+
+ def validate([], _), do: {:validation_failure, :not_enough_args}
+ def validate([_], _), do: :ok
+ def validate(_, _), do: {:validation_failure, :too_many_args}
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppStopped
+
+ def run([target_node], %{node: node_name, ram: ram, disc: disc} = opts) do
+ node_type =
+ case {ram, disc} do
+ {true, false} -> :ram
+ {false, true} -> :disc
+ ## disc is default
+ {false, false} -> :disc
+ end
+
+ long_or_short_names = Config.get_option(:longnames, opts)
+ target_node_normalised = Helpers.normalise_node(target_node, long_or_short_names)
+
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_mnesia,
+ :join_cluster,
+ [target_node_normalised, node_type]
+ )
+ end
+
+ def output({:ok, :already_member}, _) do
+ {:ok, "The node is already a member of this cluster"}
+ end
+
+ def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
+ end
+
+ def output({:error, :cannot_cluster_node_with_itself}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: cannot cluster node with itself: #{node_name}"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([target_node], %{node: node_name}) do
+ "Clustering node #{node_name} with #{target_node}"
+ end
+
+ def usage() do
+ "join_cluster [--disc|--ram] <existing_cluster_member>"
+ end
+
+ def usage_additional() do
+ [
+ ["<existing_cluster_member>", "Existing cluster member (node) to join"],
+ ["--disc", "new node should be a disk one (stores its schema on disk). Highly recommended, used by default."],
+ ["--ram", "new node should be a RAM one (stores schema in RAM). Not recommended. Consult clustering doc guides first."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering(),
+ DocGuide.cluster_formation()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Instructs the node to become a member of the cluster that the specified node is in"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_bindings_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_bindings_command.ex
new file mode 100644
index 0000000000..19e8844089
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_bindings_command.ex
@@ -0,0 +1,74 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListBindingsCommand do
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @info_keys ~w(source_name source_kind destination_name destination_kind routing_key arguments)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults([], opts) do
+ merge_defaults(
+ ~w(source_name source_kind
+ destination_name destination_kind
+ routing_key arguments),
+ opts
+ )
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout, vhost: vhost}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ RpcStream.receive_list_items(
+ node_name,
+ :rabbit_binding,
+ :info_all,
+ [vhost, info_keys],
+ timeout,
+ info_keys
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage() do
+ "list_bindings [--vhost <vhost>] [--no-table-headers] [<column> ...]"
+ end
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists all bindings on a vhost"
+
+ def banner(_, %{vhost: vhost}), do: "Listing bindings for vhost #{vhost}..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex
new file mode 100644
index 0000000000..5ae7450da1
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex
@@ -0,0 +1,85 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+##
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListChannelsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ @info_keys ~w(pid connection name number user vhost transactional
+ confirm consumer_count messages_unacknowledged
+ messages_uncommitted acks_uncommitted messages_unconfirmed
+ prefetch_count global_prefetch_count)a
+
+ def info_keys(), do: @info_keys
+
+ def merge_defaults([], opts) do
+ merge_defaults(~w(pid user consumer_count messages_unacknowledged), opts)
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], opts) do
+ run(~w(pid user consumer_count messages_unacknowledged), opts)
+ end
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ Helpers.with_nodes_in_cluster(node_name, fn nodes ->
+ RpcStream.receive_list_items(
+ node_name,
+ :rabbit_channel,
+ :emit_info_all,
+ [nodes, info_keys],
+ timeout,
+ info_keys,
+ Kernel.length(nodes)
+ )
+ end)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def banner(_, _), do: "Listing channels ..."
+
+ def usage() do
+ "list_channels [--no-table-headers] [<column> ...]"
+ end
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.channels()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists all channels in the node"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_ciphers_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_ciphers_command.ex
new file mode 100644
index 0000000000..eb7075d261
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_ciphers_command.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListCiphersCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def distribution(_), do: :none
+
+ def run(_, _) do
+ {:ok, :rabbit_pbe.supported_ciphers()}
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "list_ciphers"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.tls()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists cipher suites supported by encoding commands"
+
+ def banner(_, _), do: "Listing supported ciphers ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex
new file mode 100644
index 0000000000..0e28272ea8
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex
@@ -0,0 +1,84 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ @info_keys ~w(pid name port host peer_port peer_host ssl ssl_protocol
+ ssl_key_exchange ssl_cipher ssl_hash peer_cert_subject
+ peer_cert_issuer peer_cert_validity state
+ channels protocol auth_mechanism user vhost timeout frame_max
+ channel_max client_properties recv_oct recv_cnt send_oct
+ send_cnt send_pend connected_at)a
+
+ def info_keys(), do: @info_keys
+
+ def merge_defaults([], opts) do
+ merge_defaults(~w(user peer_host peer_port state), opts)
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ Helpers.with_nodes_in_cluster(node_name, fn nodes ->
+ RpcStream.receive_list_items(
+ node_name,
+ :rabbit_networking,
+ :emit_connection_info_all,
+ [nodes, info_keys],
+ timeout,
+ info_keys,
+ Kernel.length(nodes)
+ )
+ end)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage() do
+ "list_connections [--no-table-headers] [<column> ...]"
+ end
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.connections()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists AMQP 0.9.1 connections for the node"
+
+ def banner(_, _), do: "Listing connections ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_consumers_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_consumers_command.ex
new file mode 100644
index 0000000000..90c587cbe8
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_consumers_command.ex
@@ -0,0 +1,108 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListConsumersCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ @info_keys ~w(queue_name channel_pid consumer_tag
+ ack_required prefetch_count active activity_status arguments)a
+
+ def info_keys(), do: @info_keys
+
+ def merge_defaults([], opts) do
+ {Enum.map(@info_keys -- [:activity_status], &Atom.to_string/1),
+ Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout, vhost: vhost}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ Helpers.with_nodes_in_cluster(node_name, fn nodes ->
+ RpcStream.receive_list_items_with_fun(
+ node_name,
+ [{:rabbit_amqqueue,
+ :emit_consumers_all,
+ [nodes, vhost]}],
+ timeout,
+ info_keys,
+ Kernel.length(nodes),
+ fn item -> fill_consumer_active_fields(item) end
+ )
+ end)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage() do
+ "list_consumers [--vhost <vhost>] [--no-table-headers] [<column> ...]"
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists all consumers for a vhost"
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.consumers()
+ ]
+ end
+
+ def banner(_, %{vhost: vhost}), do: "Listing consumers in vhost #{vhost} ..."
+
+ #
+ # Implementation
+ #
+
+ # add missing fields if response comes from node < 3.8
+ def fill_consumer_active_fields({[], {chunk, :continue}}) do
+ {[], {chunk, :continue}}
+ end
+
+ def fill_consumer_active_fields({items, {chunk, :continue}}) do
+ {Enum.map(items, fn item ->
+ case Keyword.has_key?(item, :active) do
+ true ->
+ item
+ false ->
+ Keyword.drop(item, [:arguments])
+ ++ [active: true, activity_status: :up]
+ ++ [arguments: Keyword.get(item, :arguments, [])]
+ end
+ end), {chunk, :continue}}
+ end
+
+ def fill_consumer_active_fields(v) do
+ v
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_exchanges_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_exchanges_command.ex
new file mode 100644
index 0000000000..a3b8b3521b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_exchanges_command.ex
@@ -0,0 +1,67 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListExchangesCommand do
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @info_keys ~w(name type durable auto_delete internal arguments policy)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults([], opts) do
+ merge_defaults(~w(name type), opts)
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout, vhost: vhost}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ RpcStream.receive_list_items(
+ node_name,
+ :rabbit_exchange,
+ :info_all,
+ [vhost, info_keys],
+ timeout,
+ info_keys
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage(), do: "list_exchanges [--vhost <vhost>] [--no-table-headers] [<column> ...]"
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists exchanges"
+
+ def banner(_, %{vhost: vhost}), do: "Listing exchanges for vhost #{vhost} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_feature_flags_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_feature_flags_command.ex
new file mode 100644
index 0000000000..46b4bc82c2
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_feature_flags_command.ex
@@ -0,0 +1,94 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2018-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListFeatureFlagsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ alias RabbitMQ.CLI.Ctl.InfoKeys
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ @info_keys ~w(name state stability provided_by desc doc_url)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults([], opts), do: {["name", "state"], opts}
+ def merge_defaults(args, opts), do: {args, opts}
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &Validators.rabbit_is_loaded/2,
+ &Validators.rabbit_is_running/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run([_|_] = args, %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_ff_extra, :cli_info, [], timeout) do
+ # Server does not support feature flags, consider none are available.
+ # See rabbitmq/rabbitmq-cli#344 for context. MK.
+ {:badrpc, {:EXIT, {:undef, _}}} -> []
+ {:badrpc, _} = err -> err
+ val -> filter_by_arg(val, args)
+ end
+
+ end
+
+ def banner(_, _), do: "Listing feature flags ..."
+
+ def usage, do: "list_feature_flags [<column> ...]"
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.feature_flags()
+ ]
+ end
+
+ def help_section(), do: :feature_flags
+
+ def description(), do: "Lists feature flags"
+
+ #
+ # Implementation
+ #
+
+ defp filter_by_arg(ff_info, _) when is_tuple(ff_info) do
+ # tuple means unexpected data
+ ff_info
+ end
+
+ defp filter_by_arg(ff_info, [_|_] = args) when is_list(ff_info) do
+ symbol_args = InfoKeys.prepare_info_keys(args)
+ Enum.map(ff_info,
+ fn(ff) ->
+ symbol_args
+ |> Enum.filter(fn(arg) -> ff[arg] != nil end)
+ |> Enum.map(fn(arg) -> {arg, ff[arg]} end)
+ end
+ )
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_global_parameters_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_global_parameters_command.ex
new file mode 100644
index 0000000000..8d3f2d795a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_global_parameters_command.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListGlobalParametersCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :list_global_formatted,
+ [],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_global_parameters [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Lists global runtime parameters"
+
+ def banner(_, _), do: "Listing global runtime parameters ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_hashes_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_hashes_command.ex
new file mode 100644
index 0000000000..9e0f25e6dd
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_hashes_command.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListHashesCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def distribution(_), do: :none
+
+ def run(_, _) do
+ {:ok, :rabbit_pbe.supported_hashes()}
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "list_hashes"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.tls()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists hash functions supported by encoding commands"
+
+ def banner(_, _), do: "Listing supported hash algorithms ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_operator_policies_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_operator_policies_command.ex
new file mode 100644
index 0000000000..dd2c54dfc0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_operator_policies_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListOperatorPoliciesCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_policy,
+ :list_formatted_op,
+ [vhost],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_operator_policies [--vhost <vhost>] [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Lists operator policy overrides for a virtual host"
+
+ def banner(_, %{vhost: vhost}),
+ do: "Listing operator policy overrides for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_parameters_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_parameters_command.ex
new file mode 100644
index 0000000000..2d51f08527
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_parameters_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListParametersCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :list_formatted,
+ [vhost],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_parameters [--vhost <vhost>] [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Lists runtime parameters for a virtual host"
+
+ def banner(_, %{vhost: vhost}), do: "Listing runtime parameters for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_permissions_command.ex
new file mode 100644
index 0000000000..feaf917cfa
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_permissions_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListPermissionsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :list_vhost_permissions,
+ [vhost],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_permissions [--vhost <vhost>] [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Lists user permissions in a virtual host"
+
+ def banner(_, %{vhost: vhost}), do: "Listing permissions for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_policies_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_policies_command.ex
new file mode 100644
index 0000000000..9fe8e37dc1
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_policies_command.ex
@@ -0,0 +1,49 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListPoliciesCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_policy,
+ :list_formatted,
+ [vhost],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_policies [--vhost <vhost>] [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Lists all policies in a virtual host"
+
+ def banner(_, %{vhost: vhost}), do: "Listing policies for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex
new file mode 100644
index 0000000000..21f9fa78ec
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex
@@ -0,0 +1,143 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand do
+ require RabbitMQ.CLI.Ctl.InfoKeys
+ require RabbitMQ.CLI.Ctl.RpcStream
+
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 60_000
+ @info_keys ~w(name durable auto_delete
+ arguments policy pid owner_pid exclusive exclusive_consumer_pid
+ exclusive_consumer_tag messages_ready messages_unacknowledged messages
+ messages_ready_ram messages_unacknowledged_ram messages_ram
+ messages_persistent message_bytes message_bytes_ready
+ message_bytes_unacknowledged message_bytes_ram message_bytes_persistent
+ head_message_timestamp disk_reads disk_writes consumers
+ consumer_utilisation memory slave_pids synchronised_slave_pids state type
+ leader members online)a
+
+ def description(), do: "Lists queues and their properties"
+ def usage(), do: "list_queues [--vhost <vhost>] [--online] [--offline] [--local] [--no-table-headers] [<column>, ...]"
+ def scopes(), do: [:ctl, :diagnostics]
+ def switches(), do: [offline: :boolean,
+ online: :boolean,
+ local: :boolean,
+ timeout: :integer]
+ def aliases(), do: [t: :timeout]
+
+ def info_keys(), do: @info_keys
+
+ defp default_opts() do
+ %{vhost: "/", offline: false, online: false, local: false, table_headers: true}
+ end
+
+ def merge_defaults([_ | _] = args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args,
+ Map.merge(
+ default_opts(),
+ Map.merge(opts, %{timeout: timeout})
+ )}
+ end
+
+ def merge_defaults([], opts) do
+ merge_defaults(~w(name messages), opts)
+ end
+
+ def validate(args, _opts) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ # note that --offline for this command has a different meaning:
+ # it lists queues with unavailable masters
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{
+ node: node_name,
+ timeout: timeout,
+ vhost: vhost,
+ online: online_opt,
+ offline: offline_opt,
+ local: local_opt
+ }) do
+ {online, offline} =
+ case {online_opt, offline_opt} do
+ {false, false} -> {true, true}
+ other -> other
+ end
+
+ info_keys = InfoKeys.prepare_info_keys(args)
+
+ Helpers.with_nodes_in_cluster(node_name, fn nodes ->
+ offline_mfa = {:rabbit_amqqueue, :emit_info_down, [vhost, info_keys]}
+ local_mfa = {:rabbit_amqqueue, :emit_info_local, [vhost, info_keys]}
+ online_mfa = {:rabbit_amqqueue, :emit_info_all, [nodes, vhost, info_keys]}
+
+ {chunks, mfas} =
+ case {local_opt, offline, online} do
+ # Local takes precedence
+ {true, _, _} -> {1, [local_mfa]}
+ {_, true, true} -> {Kernel.length(nodes) + 1, [offline_mfa, online_mfa]}
+ {_, false, true} -> {Kernel.length(nodes), [online_mfa]}
+ {_, true, false} -> {1, [offline_mfa]}
+ end
+
+ RpcStream.receive_list_items_with_fun(node_name, mfas, timeout, info_keys, chunks, fn
+ {{:error, {:badrpc, {:timeout, to}}}, :finished} ->
+ {{:error,
+ {:badrpc,
+ {:timeout, to,
+ "Some queue(s) are unresponsive, use list_unresponsive_queues command."}}},
+ :finished}
+
+ any ->
+ any
+ end)
+ end)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def help_section(), do: :observability_and_health_checks
+
+ def usage_additional do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")],
+ ["--online", "lists only queues on online (reachable) nodes"],
+ ["--offline", "lists only queues on offline (unreachable) nodes"],
+ ["--local", "only return queues hosted on the target node"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.queues()
+ ]
+ end
+
+ def banner(_, %{vhost: vhost, timeout: timeout}) do
+ [
+ "Timeout: #{timeout / 1000} seconds ...",
+ "Listing queues for vhost #{vhost} ..."
+ ]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_topic_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_topic_permissions_command.ex
new file mode 100644
index 0000000000..1a22b3b26d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_topic_permissions_command.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListTopicPermissionsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :list_vhost_topic_permissions,
+ [vhost],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_topic_permissions [--vhost <vhost>] [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Lists topic permissions in a virtual host"
+
+ def banner(_, %{vhost: vhost}), do: "Listing topic permissions for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex
new file mode 100644
index 0000000000..91d6d624f5
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex
@@ -0,0 +1,95 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListUnresponsiveQueuesCommand do
+ require RabbitMQ.CLI.Ctl.InfoKeys
+ require RabbitMQ.CLI.Ctl.RpcStream
+
+ alias RabbitMQ.CLI.Ctl.{InfoKeys, RpcStream}
+ alias RabbitMQ.CLI.Core.Helpers
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @info_keys ~w(name durable auto_delete
+ arguments pid recoverable_slaves)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ def switches() do
+ [queue_timeout: :integer, local: :boolean, timeout: :integer]
+ end
+
+ def aliases(), do: [t: :timeout]
+
+ defp default_opts() do
+ %{vhost: "/", local: false, queue_timeout: 15, table_headers: true}
+ end
+
+ def merge_defaults([_ | _] = args, opts) do
+ {args, Map.merge(default_opts(), opts)}
+ end
+
+ def merge_defaults([], opts) do
+ merge_defaults(~w(name), opts)
+ end
+
+ def validate(args, _opts) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run(args, %{
+ node: node_name,
+ vhost: vhost,
+ timeout: timeout,
+ queue_timeout: qtimeout,
+ local: local_opt
+ }) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+ queue_timeout = qtimeout * 1000
+
+ Helpers.with_nodes_in_cluster(node_name, fn nodes ->
+ local_mfa = {:rabbit_amqqueue, :emit_unresponsive_local, [vhost, info_keys, queue_timeout]}
+ all_mfa = {:rabbit_amqqueue, :emit_unresponsive, [nodes, vhost, info_keys, queue_timeout]}
+
+ {chunks, mfas} =
+ case local_opt do
+ true -> {1, [local_mfa]}
+ false -> {Kernel.length(nodes), [all_mfa]}
+ end
+
+ RpcStream.receive_list_items(node_name, mfas, timeout, info_keys, chunks)
+ end)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def banner(_, %{vhost: vhost}), do: "Listing unresponsive queues for vhost #{vhost} ..."
+
+ def usage() do
+ "list_unresponsive_queues [--local] [--queue-timeout <milliseconds>] [<column> ...] [--no-table-headers]"
+ end
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")],
+ ["--local", "only return queues hosted on the target node"],
+ ["--queue-timeout <milliseconds>", "per-queue timeout to use when checking for responsiveness"]
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Tests queues to respond within timeout. Lists those which did not respond"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_limits_command.ex
new file mode 100644
index 0000000000..5e0de38b3f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_limits_command.ex
@@ -0,0 +1,91 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListUserLimitsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ def switches(), do: [global: :boolean, user: :string]
+
+ def merge_defaults(args, %{global: true} = opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{user: "guest", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, global: true}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :get_user_limits, []) do
+ [] ->
+ []
+
+ {:error, err} ->
+ {:error, err}
+
+ {:badrpc, node} ->
+ {:badrpc, node}
+
+ val ->
+ Enum.map(val, fn {user, val} ->
+ {:ok, val_encoded} = JSON.encode(Map.new(val))
+ [user: user, limits: val_encoded]
+ end)
+ end
+ end
+
+ def run([], %{node: node_name, user: username}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :get_user_limits, [username]) do
+ :undefined ->
+ {:error, {:no_such_user, username}}
+
+ {:error, err} ->
+ {:error, err}
+
+ {:badrpc, node} ->
+ {:badrpc, node}
+
+ val when is_list(val) or is_map(val) ->
+ {:ok, val_encoded} = JSON.encode(Map.new(val))
+ val_encoded
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_user_limits [--user <username>] [--global]"
+
+ def usage_additional() do
+ [
+ ["--global", "list limits for all the users"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Displays configured user limits"
+
+ def banner([], %{global: true}) do
+ "Listing limits for all users ..."
+ end
+
+ def banner([], %{user: username}) do
+ "Listing limits for user \"#{username}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_permissions_command.ex
new file mode 100644
index 0000000000..bd302eefd0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_permissions_command.ex
@@ -0,0 +1,58 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListUserPermissionsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def scopes(), do: [:ctl, :diagnostics]
+ def switches(), do: [timeout: :integer]
+ def aliases(), do: [t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def validate([], _), do: {:validation_failure, :not_enough_args}
+ def validate([_ | _] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
+ def validate([_], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :list_user_permissions,
+ [username],
+ timeout
+ )
+ end
+
+ def usage, do: "list_user_permissions [--no-table-headers] <username>"
+
+ def usage_additional do
+ [
+ ["<username>", "Name of the user"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Lists permissions of a user across all virtual hosts"
+
+ def banner([username], _), do: "Listing permissions for user \"#{username}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_topic_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_topic_permissions_command.ex
new file mode 100644
index 0000000000..48b7fee5e2
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_user_topic_permissions_command.ex
@@ -0,0 +1,54 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListUserTopicPermissionsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: false}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :list_user_topic_permissions,
+ [username],
+ timeout
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_user_topic_permissions [--no-table-headers] <username>"
+
+ def usage_additional do
+ [
+ ["<username>", "Name of the user"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Lists user topic permissions"
+
+ def banner([username], _), do: "Listing topic permissions for user \"#{username}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_users_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_users_command.ex
new file mode 100644
index 0000000000..e87ea386d0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_users_command.ex
@@ -0,0 +1,43 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListUsersCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :list_users, [], timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_users [--no-table-headers]"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "List user names and tags"
+
+ def banner(_, _), do: "Listing users ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhost_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhost_limits_command.ex
new file mode 100644
index 0000000000..67b138f1e0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhost_limits_command.ex
@@ -0,0 +1,90 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListVhostLimitsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+ def switches(), do: [global: :boolean]
+
+ def merge_defaults(args, %{global: true} = opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", table_headers: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, global: true}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_vhost_limit, :list, []) do
+ [] ->
+ []
+
+ {:error, err} ->
+ {:error, err}
+
+ {:badrpc, node} ->
+ {:badrpc, node}
+
+ val ->
+ Enum.map(val, fn {vhost, val} ->
+ {:ok, val_encoded} = JSON.encode(Map.new(val))
+ [vhost: vhost, limits: val_encoded]
+ end)
+ end
+ end
+
+ def run([], %{node: node_name, vhost: vhost}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_vhost_limit, :list, [vhost]) do
+ [] ->
+ []
+
+ {:error, err} ->
+ {:error, err}
+
+ {:badrpc, node} ->
+ {:badrpc, node}
+
+ val when is_list(val) or is_map(val) ->
+ JSON.encode(Map.new(val))
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_vhost_limits [--vhost <vhost>] [--global] [--no-table-headers]"
+
+ def usage_additional() do
+ [
+ ["--global", "list global limits (those not associated with a virtual host)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Displays configured virtual host limits"
+
+ def banner([], %{global: true}) do
+ "Listing limits for all vhosts ..."
+ end
+
+ def banner([], %{vhost: vhost}) do
+ "Listing limits for vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhosts_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhosts_command.ex
new file mode 100644
index 0000000000..b570aa7486
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_vhosts_command.ex
@@ -0,0 +1,84 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ListVhostsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ alias RabbitMQ.CLI.Ctl.InfoKeys
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ @info_keys ~w(name description tags tracing cluster_state)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults([], opts) do
+ merge_defaults(["name"], opts)
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost, :info_all, [], timeout)
+ |> filter_by_arg(args)
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "list_vhosts [--no-table-headers] [<column> ...]"
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts(),
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+
+ def description(), do: "Lists virtual hosts"
+
+ def banner(_, _), do: "Listing vhosts ..."
+
+ #
+ # Implementation
+ #
+
+ defp filter_by_arg(vhosts, _) when is_tuple(vhosts) do
+ vhosts
+ end
+
+ defp filter_by_arg(vhosts, [_ | _] = args) do
+ symbol_args = InfoKeys.prepare_info_keys(args)
+
+ vhosts
+ |> Enum.map(fn vhost ->
+ symbol_args
+ |> Enum.filter(fn arg -> vhost[arg] != nil end)
+ |> Enum.map(fn arg -> {arg, vhost[arg]} end)
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/node_health_check_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/node_health_check_command.ex
new file mode 100644
index 0000000000..31ea748d9f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/node_health_check_command.ex
@@ -0,0 +1,87 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.NodeHealthCheckCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 70_000
+
+ def scopes(), do: [:ctl, :diagnostics]
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(opts, %{timeout: timeout})}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_health_check, :node, [node_name, timeout]) do
+ :ok ->
+ :ok
+
+ true ->
+ :ok
+
+ {:badrpc, _} = err ->
+ err
+
+ {:error_string, error_message} ->
+ {:healthcheck_failed, error_message}
+
+ {:node_is_ko, error_message, _exit_code} ->
+ {:healthcheck_failed, error_message}
+
+ other ->
+ other
+ end
+ end
+
+ def output(:ok, _) do
+ {:ok, "Health check passed"}
+ end
+
+ def output({:healthcheck_failed, message}, _) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: health check failed. Message: #{message}"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "node_health_check"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :deprecated
+ def description() do
+ "DEPRECATED. Performs intrusive, opinionated health checks on a fully booted node. " <>
+ "See https://www.rabbitmq.com/monitoring.html#health-checks instead"
+ end
+
+ def banner(_, %{node: node_name, timeout: timeout}) do
+ [
+ "This command is DEPRECATED and will be removed in a future version.",
+ "It performs intrusive, opinionated health checks and requires a fully booted node.",
+ "Use one of the options covered in https://www.rabbitmq.com/monitoring.html#health-checks instead.",
+ "Timeout: #{trunc(timeout / 1000)} seconds ...",
+ "Checking health of node #{node_name} ..."
+ ]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/ping_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/ping_command.ex
new file mode 100644
index 0000000000..7efb3b39f3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/ping_command.ex
@@ -0,0 +1,90 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.PingCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 60_000
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(opts, %{timeout: timeout})}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # this is very similar to what net_adm:ping/1 does reimplemented with support for custom timeouts
+ # and error values that are used by CLI commands
+ msg = "Failed to connect and authenticate to #{node_name} in #{timeout} ms"
+
+ try do
+ case :gen.call({:net_kernel, node_name}, :"$gen_call", {:is_auth, node()}, timeout) do
+ :ok ->
+ :ok
+
+ {:ok, _} ->
+ :ok
+
+ _ ->
+ :erlang.disconnect_node(node_name)
+ {:error, msg}
+ end
+ catch
+ :exit, _ ->
+ :erlang.disconnect_node(node_name)
+ {:error, msg}
+
+ _ ->
+ :erlang.disconnect_node(node_name)
+ {:error, msg}
+ end
+ end
+
+ def output(:ok, _) do
+ {:ok, "Ping succeeded"}
+ end
+
+ def output({:error, :timeout}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: timed out while waiting for a response from #{node_name}."}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "ping"
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Checks that the node OS process is up, registered with EPMD and CLI tools can authenticate with it"
+
+ def banner([], %{node: node_name, timeout: timeout}) when is_number(timeout) do
+ "Will ping #{node_name}. This only checks if the OS process is running and registered with epmd. Timeout: #{
+ timeout
+ } ms."
+ end
+
+ def banner([], %{node: node_name, timeout: _timeout}) do
+ "Will ping #{node_name}. This only checks if the OS process is running and registered with epmd."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/purge_queue_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/purge_queue_command.ex
new file mode 100644
index 0000000000..1be25beb7d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/purge_queue_command.ex
@@ -0,0 +1,73 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.PurgeQueueCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([queue], %{node: node_name, vhost: vhost, timeout: timeout}) do
+ res =
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_amqqueue,
+ :lookup,
+ [:rabbit_misc.r(vhost, :queue, queue)],
+ timeout
+ )
+
+ case res do
+ {:ok, q} -> purge(node_name, q, timeout)
+ _ -> res
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "purge_queue <queue>"
+
+ def usage_additional() do
+ [
+ ["<queue>", "Name of the queue to purge"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.queues()
+ ]
+ end
+
+ def help_section(), do: :queues
+
+ def description(), do: "Purges a queue (removes all messages in it)"
+
+ def banner([queue], %{vhost: vhost}) do
+ "Purging queue '#{queue}' in vhost '#{vhost}' ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp purge(node_name, q, timeout) do
+ res = :rabbit_misc.rpc_call(node_name, :rabbit_amqqueue, :purge, [q], timeout)
+
+ case res do
+ {:ok, _message_count} -> :ok
+ _ -> res
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rename_cluster_node_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rename_cluster_node_command.ex
new file mode 100644
index 0000000000..7faa30d00b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rename_cluster_node_command.ex
@@ -0,0 +1,108 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.RenameClusterNodeCommand do
+ require Integer
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts), do: {args, opts}
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(_, _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &validate_args_count_even/2,
+ &Validators.node_is_not_running/2,
+ &Validators.mnesia_dir_is_set/2,
+ &Validators.feature_flags_file_is_set/2,
+ &Validators.rabbit_is_loaded/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run(nodes, %{node: node_name}) do
+ node_pairs = make_node_pairs(nodes)
+
+ try do
+ :rabbit_mnesia_rename.rename(node_name, node_pairs)
+ catch
+ _, reason ->
+ {:rename_failed, reason}
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "rename_cluster_node <oldnode1> <newnode1> [oldnode2] [newnode2] ..."
+ end
+
+ def usage_additional() do
+ [
+ ["<oldnode>", "Original node name"],
+ ["<newnode>", "New node name"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Renames cluster nodes in the local database"
+
+ def banner(args, _) do
+ [
+ "Renaming cluster nodes: \n ",
+ for {node_from, node_to} <- make_node_pairs(args) do
+ "#{node_from} -> #{node_to} \n"
+ end
+ ]
+ |> List.flatten()
+ |> Enum.join()
+ end
+
+ #
+ # Implementation
+ #
+
+ defp validate_args_count_even(args, _) do
+ case agrs_count_even?(args) do
+ true ->
+ :ok
+
+ false ->
+ {:validation_failure,
+ {:bad_argument, "Argument list should contain even number of nodes"}}
+ end
+ end
+
+ defp agrs_count_even?(args) do
+ Integer.is_even(length(args))
+ end
+
+ defp make_node_pairs([]) do
+ []
+ end
+
+ defp make_node_pairs([from, to | rest]) do
+ [{to_atom(from), to_atom(to)} | make_node_pairs(rest)]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/report_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/report_command.ex
new file mode 100644
index 0000000000..c06497a7e6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/report_command.ex
@@ -0,0 +1,118 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ReportCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ alias RabbitMQ.CLI.Ctl.Commands.{
+ ClusterStatusCommand,
+ EnvironmentCommand,
+ ListBindingsCommand,
+ ListChannelsCommand,
+ ListConnectionsCommand,
+ ListExchangesCommand,
+ ListGlobalParametersCommand,
+ ListParametersCommand,
+ ListPermissionsCommand,
+ ListPoliciesCommand,
+ ListQueuesCommand,
+ StatusCommand
+ }
+ alias RabbitMQ.CLI.Diagnostics.Commands.{
+ CommandLineArgumentsCommand,
+ OsEnvCommand
+ }
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([_ | _] = args, _) when length(args) != 0,
+ do: {:validation_failure, :too_many_args}
+
+ def validate([], %{formatter: formatter}) do
+ case formatter do
+ "report" -> :ok
+ _other -> {:validation_failure, "Only report formatter is supported"}
+ end
+ end
+
+ def validate([], _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name} = opts) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_vhost, :list_names, []) do
+ {:badrpc, _} = err ->
+ err
+
+ vhosts ->
+ data = [
+ run_command(StatusCommand, [], opts),
+ run_command(ClusterStatusCommand, [], opts),
+ run_command(EnvironmentCommand, [], opts),
+ run_command(ListConnectionsCommand, info_keys(ListConnectionsCommand), opts),
+ run_command(ListChannelsCommand, info_keys(ListChannelsCommand), opts),
+ run_command(CommandLineArgumentsCommand, [], opts),
+ run_command(OsEnvCommand, [], opts)
+ ]
+
+ vhost_data =
+ vhosts
+ |> Enum.flat_map(fn v ->
+ opts = Map.put(opts, :vhost, v)
+
+ [
+ run_command(ListQueuesCommand, info_keys(ListQueuesCommand), opts),
+ run_command(ListExchangesCommand, info_keys(ListExchangesCommand), opts),
+ run_command(ListBindingsCommand, info_keys(ListBindingsCommand), opts),
+ run_command(ListPermissionsCommand, [], opts),
+ run_command(ListPoliciesCommand, [], opts),
+ run_command(ListGlobalParametersCommand, [], opts),
+ run_command(ListParametersCommand, [], opts),
+
+ ]
+ end)
+
+ data ++ vhost_data
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Report
+
+ def usage, do: "report"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Generate a server status report containing a concatenation of all server status information for support purposes"
+
+ def banner(_, %{node: node_name}), do: "Reporting server status of node #{node_name} ..."
+
+ #
+ # Implementation
+ #
+
+ defp run_command(command, args, opts) do
+ {args, opts} = command.merge_defaults(args, opts)
+ banner = command.banner(args, opts)
+ command_result = command.run(args, opts) |> command.output(opts)
+ {command, banner, command_result}
+ end
+
+ defp info_keys(command) do
+ command.info_keys()
+ |> Enum.map(&to_string/1)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/reset_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/reset_command.ex
new file mode 100644
index 0000000000..575ef2491d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/reset_command.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ResetCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppStopped
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :reset, [])
+ end
+
+ def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "reset"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Instructs a RabbitMQ node to leave the cluster and return to its virgin state"
+
+ def banner(_, %{node: node_name}), do: "Resetting node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/restart_vhost_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/restart_vhost_command.ex
new file mode 100644
index 0000000000..36a7f702bc
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/restart_vhost_command.ex
@@ -0,0 +1,62 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+alias RabbitMQ.CLI.Core.ExitCodes
+
+defmodule RabbitMQ.CLI.Ctl.Commands.RestartVhostCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts), do: {args, Map.merge(%{vhost: "/"}, opts)}
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, vhost: vhost, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost_sup_sup, :start_vhost, [vhost], timeout)
+ end
+
+ def output({:ok, _pid}, %{vhost: vhost, node: node_name}) do
+ {:ok, "Successfully restarted vhost '#{vhost}' on node '#{node_name}'"}
+ end
+
+ def output({:error, {:already_started, _pid}}, %{vhost: vhost, node: node_name}) do
+ {:ok, "Vhost '#{vhost}' is already running on node '#{node_name}'"}
+ end
+
+ def output({:error, err}, %{vhost: vhost, node: node_name}) do
+ {:error, ExitCodes.exit_software(),
+ ["Failed to start vhost '#{vhost}' on node '#{node_name}'", "Reason: #{inspect(err)}"]}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "restart_vhost [--vhost <vhost>]"
+
+ def usage_additional() do
+ [
+ ["--vhost", "Virtual host name"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Restarts a failed vhost data stores and queues"
+
+ def banner(_, %{node: node_name, vhost: vhost}) do
+ "Trying to restart vhost '#{vhost}' on node '#{node_name}' ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/resume_listeners_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/resume_listeners_command.ex
new file mode 100644
index 0000000000..1f13660e0d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/resume_listeners_command.ex
@@ -0,0 +1,45 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ResumeListenersCommand do
+ @moduledoc """
+ Resumes all client connection listeners making them accept new client
+ connections. This command is the opposite of `SuspendListenersCommand`.
+
+ This command is meant to be used when automating upgrades.
+ See also `SuspendListenersCommand`.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_maintenance, :resume_all_client_listeners, [], timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "resume_listeners"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Resumes client connection listeners making them accept client connections again"
+
+ def banner(_, %{node: node_name}) do
+ "Will resume client connection listeners on node #{node_name}. "
+ <> "The node will now accept client connections"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rotate_logs_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rotate_logs_command.ex
new file mode 100644
index 0000000000..f3de3671fc
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/rotate_logs_command.ex
@@ -0,0 +1,34 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.RotateLogsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :rotate_logs, [])
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "rotate_logs"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.logging()
+ ]
+ end
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Instructs the RabbitMQ node to perform internal log rotation"
+
+ def banner(_, %{node: node_name}), do: "Rotating logs for node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_cluster_name_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_cluster_name_command.ex
new file mode 100644
index 0000000000..f919cb2ae6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_cluster_name_command.ex
@@ -0,0 +1,47 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetClusterNameCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([cluster_name], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_nodes, :set_cluster_name, [
+ cluster_name,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([cluster_name], _) do
+ "Setting cluster name to #{cluster_name} ..."
+ end
+
+ def usage, do: "set_cluster_name <name>"
+
+ def usage_additional() do
+ [
+ ["<name>", "New cluster name"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts(),
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Sets the cluster name"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_disk_free_limit_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_disk_free_limit_command.ex
new file mode 100644
index 0000000000..cf97c4655e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_disk_free_limit_command.ex
@@ -0,0 +1,140 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetDiskFreeLimitCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.Memory
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(["mem_relative"], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(["mem_relative" | _] = args, _) when length(args) != 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([limit], _) do
+ case Integer.parse(limit) do
+ {_, ""} ->
+ :ok
+
+ {limit_val, units} ->
+ case memory_unit_absolute(limit_val, units) do
+ scaled_limit when is_integer(scaled_limit) -> :ok
+ _ -> {:validation_failure, :bad_argument}
+ end
+
+ _ ->
+ {:validation_failure, :bad_argument}
+ end
+ end
+
+ def validate(["mem_relative", fraction], _) do
+ case Float.parse(fraction) do
+ {val, ""} when val >= 0.0 -> :ok
+ _ -> {:validation_failure, :bad_argument}
+ end
+ end
+
+ def validate([_ | rest], _) when length(rest) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run(["mem_relative", _] = args, opts) do
+ set_disk_free_limit_relative(args, opts)
+ end
+
+ def run([limit], %{node: _} = opts) when is_binary(limit) do
+ case Integer.parse(limit) do
+ {limit_val, ""} -> set_disk_free_limit_absolute([limit_val], opts)
+ {limit_val, units} -> set_disk_free_limit_in_units([limit_val, units], opts)
+ end
+ end
+
+ def run([limit], opts) do
+ set_disk_free_limit_absolute([limit], opts)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner(["mem_relative", arg], %{node: node_name}) do
+ "Setting disk free limit on #{node_name} to #{arg} times the total RAM ..."
+ end
+
+ def banner([arg], %{node: node_name}),
+ do: "Setting disk free limit on #{node_name} to #{arg} bytes ..."
+
+ def usage, do: "set_disk_free_limit <disk_limit> | mem_relative <fraction>"
+
+ def usage_additional() do
+ [
+ ["<disk_limit>", "New limit as an absolute value with units, e.g. 1GB"],
+ ["mem_relative <fraction>", "New limit as a fraction of total memory reported by the OS"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.disk_alarms(),
+ DocGuide.alarms()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Sets the disk_free_limit setting"
+
+ #
+ # Implementation
+ #
+
+ defp set_disk_free_limit_relative(["mem_relative", fraction], %{node: node_name})
+ when is_float(fraction) do
+ make_rpc_call(node_name, [{:mem_relative, fraction}])
+ end
+
+ defp set_disk_free_limit_relative(["mem_relative", integer_input], %{node: node_name})
+ when is_integer(integer_input) do
+ make_rpc_call(node_name, [{:mem_relative, integer_input * 1.0}])
+ end
+
+ defp set_disk_free_limit_relative(["mem_relative", fraction_str], %{node: _} = opts)
+ when is_binary(fraction_str) do
+ {fraction_val, ""} = Float.parse(fraction_str)
+ set_disk_free_limit_relative(["mem_relative", fraction_val], opts)
+ end
+
+ ## ------------------------ Absolute Size Call -----------------------------
+
+ defp set_disk_free_limit_absolute([limit], %{node: node_name}) when is_integer(limit) do
+ make_rpc_call(node_name, [limit])
+ end
+
+ defp set_disk_free_limit_absolute([limit], %{node: _} = opts) when is_float(limit) do
+ set_disk_free_limit_absolute([limit |> Float.floor() |> round], opts)
+ end
+
+ defp set_disk_free_limit_in_units([limit_val, units], opts) do
+ case memory_unit_absolute(limit_val, units) do
+ scaled_limit when is_integer(scaled_limit) ->
+ set_disk_free_limit_absolute([scaled_limit], opts)
+ end
+ end
+
+ defp make_rpc_call(node_name, args) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_disk_monitor, :set_disk_free_limit, args)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_global_parameter_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_global_parameter_command.ex
new file mode 100644
index 0000000000..8c46e9d592
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_global_parameter_command.ex
@@ -0,0 +1,57 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetGlobalParameterCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, value], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :parse_set_global,
+ [name, value, Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_global_parameter <name> <value>"
+
+ def usage_additional() do
+ [
+ ["<name>", "global parameter name (identifier)"],
+ ["<value>", "parameter value"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Sets a runtime parameter."
+
+ def banner([name, value], _) do
+ "Setting global runtime parameter \"#{name}\" to \"#{value}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_log_level_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_log_level_command.ex
new file mode 100644
index 0000000000..f5a8eacbfc
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_log_level_command.ex
@@ -0,0 +1,74 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetLogLevelCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ @known_levels [
+ "debug",
+ "info",
+ "notice",
+ "warning",
+ "error",
+ "critical",
+ "alert",
+ "emergency",
+ "none"
+ ]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([level], _) do
+ case Enum.member?(@known_levels, level) do
+ true ->
+ :ok
+
+ false ->
+ {:error, "level #{level} is not supported. Try one of debug, info, warning, error, none"}
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([log_level], %{node: node_name}) do
+ arg = String.to_atom(log_level)
+ :rabbit_misc.rpc_call(node_name, :rabbit_lager, :set_log_level, [arg])
+ end
+
+ def usage, do: "set_log_level <log_level>"
+
+ def usage_additional() do
+ [
+ ["<log_level>", "new log level"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.logging()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Sets log level in the running node"
+
+ def banner([log_level], _), do: "Setting log level to \"#{log_level}\" ..."
+
+ def output({:error, {:invalid_log_level, level}}, _opts) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "level #{level} is not supported. Try one of debug, info, warning, error, none"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_operator_policy_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_operator_policy_command.ex
new file mode 100644
index 0000000000..3118c125cb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_operator_policy_command.ex
@@ -0,0 +1,79 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetOperatorPolicyCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [priority: :integer, apply_to: :string]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", priority: 0, apply_to: "all"}, opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) < 3 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 3 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, pattern, definition], %{
+ node: node_name,
+ vhost: vhost,
+ priority: priority,
+ apply_to: apply_to
+ }) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_policy,
+ :parse_set_op,
+ [vhost, name, pattern, definition, priority, apply_to, Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "set_operator_policy [--vhost <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>"
+ end
+
+ def usage_additional() do
+ [
+ ["<name>", "policy name (identifier)"],
+ ["<pattern>", "a regular expression pattern that will be used to match queue, exchanges, etc"],
+ ["<definition>", "policy definition (arguments). Must be a valid JSON document"],
+ ["--priority <priority>", "policy priority"],
+ ["--apply-to <queues | exchanges | all>", "policy should only apply to queues, exchanges, or all entities (both of the above)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Sets an operator policy that overrides a subset of arguments in user policies"
+
+ def banner([name, pattern, definition], %{vhost: vhost, priority: priority}) do
+ "Setting operator policy override \"#{name}\" for pattern \"#{pattern}\" to \"#{definition}\" with priority \"#{
+ priority
+ }\" for vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_parameter_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_parameter_command.ex
new file mode 100644
index 0000000000..910cc6ef73
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_parameter_command.ex
@@ -0,0 +1,68 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetParameterCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) < 3 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 3 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([component_name, name, value], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime_parameters,
+ :parse_set,
+ [vhost, component_name, name, value, Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_parameter [--vhost <vhost>] <component_name> <name> <value>"
+
+ def usage_additional() do
+ [
+ ["<component_name>", "component name"],
+ ["<name>", "parameter name (identifier)"],
+ ["<value>", "parameter value"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :parameters
+
+ def description(), do: "Sets a runtime parameter."
+
+ def banner([component_name, name, value], %{vhost: vhost}) do
+ "Setting runtime parameter \"#{name}\" for component \"#{component_name}\" to \"#{value}\" in vhost \"#{
+ vhost
+ }\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_command.ex
new file mode 100644
index 0000000000..c87969121c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_command.ex
@@ -0,0 +1,78 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetPermissionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) < 4 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 4 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([user, conf, write, read], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :set_permissions,
+ [user, vhost, conf, write, read, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "Virtual host #{vhost} does not exist"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User #{username} does not exist"}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, _) do
+ {:error, "Virtual host #{vhost} does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_permissions [--vhost <vhost>] <username> <conf> <write> <read>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Self-explanatory"],
+ ["<conf>", "Configuration permission pattern"],
+ ["<write>", "Write permission pattern"],
+ ["<read>", "Read permission pattern"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :access_control
+ def description(), do: "Sets user permissions for a vhost"
+
+ def banner([user | _], %{vhost: vhost}),
+ do: "Setting permissions for user \"#{user}\" in vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_policy_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_policy_command.ex
new file mode 100644
index 0000000000..af34f3c659
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_policy_command.ex
@@ -0,0 +1,76 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetPolicyCommand do
+ alias RabbitMQ.CLI.Core.{Helpers, DocGuide}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [priority: :integer, apply_to: :string]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/", priority: 0, apply_to: "all"}, opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) < 3 do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 3 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, pattern, definition], %{
+ node: node_name,
+ vhost: vhost,
+ priority: priority,
+ apply_to: apply_to
+ }) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_policy,
+ :parse_set,
+ [vhost, name, pattern, definition, priority, apply_to, Helpers.cli_acting_user()]
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "set_policy [--vhost <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>"
+ end
+
+ def usage_additional() do
+ [
+ ["<name>", "policy name (identifier)"],
+ ["<pattern>", "regular expression pattern that will be used to match queues, exchanges, etc"],
+ ["<definition>", "policy definition (arguments). Must be a valid JSON document"],
+ ["--priority <priority>", "policy priority"],
+ ["--apply-to <queues | exchanges | all>", "policy should only apply to queues, exchanges, or all entities (both of the above)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.parameters()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Sets or updates a policy"
+
+ def banner([name, pattern, definition], %{vhost: vhost, priority: priority}) do
+ "Setting policy \"#{name}\" for pattern \"#{pattern}\" to \"#{definition}\" with priority \"#{
+ priority
+ }\" for vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_topic_permissions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_topic_permissions_command.ex
new file mode 100644
index 0000000000..c57dc1659b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_topic_permissions_command.ex
@@ -0,0 +1,75 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetTopicPermissionsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ def validate(args, _) when length(args) < 4 do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 4 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([user, exchange, write_pattern, read_pattern], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :set_topic_permissions,
+ [user, vhost, exchange, write_pattern, read_pattern, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "Virtual host #{vhost} does not exist"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User #{username} does not exist"}
+ end
+ def output({:error, {:no_such_vhost, vhost}}, _) do
+ {:error, "Virtual host #{vhost} does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage do
+ "set_topic_permissions [--vhost <vhost>] <username> <exchange> <write> <read>"
+ end
+
+ def usage_additional do
+ [
+ ["<username>", "Self-explanatory"],
+ ["<exchange>", "Topic exchange to set the permissions for"],
+ ["<write>", "Write permission pattern"],
+ ["<read>", "Read permission pattern"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :access_control
+
+ def description(), do: "Sets user topic permissions for an exchange"
+
+ def banner([user, exchange, _, _], %{vhost: vhost}),
+ do:
+ "Setting topic permissions on \"#{exchange}\" for user \"#{user}\" in vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_limits_command.ex
new file mode 100644
index 0000000000..603a8008e7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_limits_command.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetUserLimitsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([username, definition], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_auth_backend_internal, :set_user_limits, [
+ username,
+ definition,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_user_limits <username> <definition>"
+
+ def usage_additional() do
+ [
+ ["<username>", "Self-explanatory"],
+ ["<definition>", "Limit definitions as a JSON document"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Sets user limits"
+
+ def banner([username, definition], %{}) do
+ "Setting user limits to \"#{definition}\" for user \"#{username}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_tags_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_tags_command.ex
new file mode 100644
index 0000000000..eba8ed6123
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_user_tags_command.ex
@@ -0,0 +1,61 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetUserTagsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts), do: {args, opts}
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([user | tags], %{node: node_name}) do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_auth_backend_internal,
+ :set_tags,
+ [user, tags, Helpers.cli_acting_user()]
+ )
+ end
+
+ def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exists"}}
+ end
+ def output({:error, {:no_such_user, username}}, _) do
+ {:error, ExitCodes.exit_nouser(), "User \"#{username}\" does not exist"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_user_tags <username> <tag> [...]"
+
+ def usage_additional() do
+ [
+ ["<username>", "Self-explanatory"],
+ ["<tags>", "Space separated list of tags"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.management(),
+ DocGuide.access_control()
+ ]
+ end
+
+ def help_section(), do: :user_management
+
+ def description(), do: "Sets user tags"
+
+ def banner([user | tags], _) do
+ "Setting tags for user \"#{user}\" to [#{tags |> Enum.join(", ")}] ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vhost_limits_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vhost_limits_command.ex
new file mode 100644
index 0000000000..f25f1c7bc4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vhost_limits_command.ex
@@ -0,0 +1,50 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetVhostLimitsCommand do
+ alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([definition], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost_limit, :parse_set, [
+ vhost,
+ definition,
+ Helpers.cli_acting_user()
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "set_vhost_limits [--vhost <vhost>] <definition>"
+
+ def usage_additional() do
+ [
+ ["<definition>", "Limit definitions, must be a valid JSON document"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def description(), do: "Sets virtual host limits"
+
+ def banner([definition], %{vhost: vhost}) do
+ "Setting vhost limits to \"#{definition}\" for vhost \"#{vhost}\" ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vm_memory_high_watermark_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vm_memory_high_watermark_command.ex
new file mode 100644
index 0000000000..a4e4527f8f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_vm_memory_high_watermark_command.ex
@@ -0,0 +1,146 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SetVmMemoryHighWatermarkCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.Memory
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(["absolute"], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(["absolute" | _] = args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(["absolute", arg], _) do
+ case Integer.parse(arg) do
+ :error ->
+ {:validation_failure, :bad_argument}
+
+ {_, rest} ->
+ case Enum.member?(memory_units(), rest) do
+ true ->
+ :ok
+
+ false ->
+ case Float.parse(arg) do
+ {_, orest} when orest == rest ->
+ {:validation_failure, {:bad_argument, "Invalid units."}}
+
+ _ ->
+ {:validation_failure, {:bad_argument, "The threshold should be an integer."}}
+ end
+ end
+ end
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([arg], _) when is_number(arg) and (arg < 0.0 or arg > 1.0) do
+ {:validation_failure,
+ {:bad_argument, "The threshold should be a fraction between 0.0 and 1.0"}}
+ end
+
+ def validate([arg], %{}) when is_binary(arg) do
+ case Float.parse(arg) do
+ {arg, ""} when is_number(arg) and (arg < 0.0 or arg > 1.0) ->
+ {:validation_failure,
+ {:bad_argument, "The threshold should be a fraction between 0.0 and 1.0"}}
+
+ {_, ""} ->
+ :ok
+
+ _ ->
+ {:validation_failure, :bad_argument}
+ end
+ end
+
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run(["absolute", arg], opts) do
+ case Integer.parse(arg) do
+ {num, rest} ->
+ valid_units = rest in memory_units()
+ set_vm_memory_high_watermark_absolute({num, rest}, valid_units, opts)
+ end
+ end
+
+ def run([arg], %{node: node_name}) when is_number(arg) and arg >= 0.0 do
+ :rabbit_misc.rpc_call(
+ node_name,
+ :vm_memory_monitor,
+ :set_vm_memory_high_watermark,
+ [arg]
+ )
+ end
+
+ def run([arg], %{} = opts) when is_binary(arg) do
+ case Float.parse(arg) do
+ {num, ""} -> run([num], opts)
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage do
+ "set_vm_memory_high_watermark <fraction> | absolute <value>"
+ end
+
+ def usage_additional() do
+ [
+ ["<fraction>", "New limit as a fraction of total memory reported by the OS"],
+ ["absolute <value>", "New limit as an absolute value with units, e.g. 1GB"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.alarms(),
+ DocGuide.memory_use(),
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Sets the vm_memory_high_watermark setting"
+
+ def banner(["absolute", arg], %{node: node_name}) do
+ "Setting memory threshold on #{node_name} to #{arg} bytes ..."
+ end
+
+ def banner([arg], %{node: node_name}) do
+ "Setting memory threshold on #{node_name} to #{arg} ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp set_vm_memory_high_watermark_absolute({num, rest}, true, %{node: node_name})
+ when num > 0 do
+ val = memory_unit_absolute(num, rest)
+
+ :rabbit_misc.rpc_call(
+ node_name,
+ :vm_memory_monitor,
+ :set_vm_memory_high_watermark,
+ [{:absolute, val}]
+ )
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex
new file mode 100644
index 0000000000..10700bf309
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/shutdown_command.ex
@@ -0,0 +1,106 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.ShutdownCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ alias RabbitMQ.CLI.Core.{OsPid, NodeName}
+
+ def switches() do
+ [timeout: :integer,
+ wait: :boolean]
+ end
+
+ def aliases(), do: [timeout: :t]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{wait: true, timeout: 120}, opts)}
+ end
+
+ def validate([], %{wait: false}) do
+ :ok
+ end
+
+ def validate([], %{node: node_name, wait: true}) do
+ local_hostname = NodeName.hostname_from_node(Node.self())
+ remote_hostname = NodeName.hostname_from_node(node_name)
+ case addressing_local_node?(local_hostname, remote_hostname) do
+ true -> :ok;
+ false ->
+ msg = "\nThis command can only --wait for shutdown of local nodes " <>
+ "but node #{node_name} seems to be remote " <>
+ "(local hostname: #{local_hostname}, remote: #{remote_hostname}).\n" <>
+ "Pass --no-wait to shut node #{node_name} down without waiting.\n"
+ {:validation_failure, {:unsupported_target, msg}}
+ end
+ end
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, wait: false, timeout: timeout}) do
+ shut_down_node_without_waiting(node_name, timeout)
+ end
+
+ def run([], %{node: node_name, wait: true, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :os, :getpid, []) do
+ pid when is_list(pid) ->
+ shut_down_node_and_wait_pid_to_stop(node_name, pid, timeout)
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "shutdown [--wait]"
+
+ def usage_additional() do
+ [
+ ["--wait", "if set, will wait for target node to terminate (by inferring and monitoring its PID file). Only works for local nodes."],
+ ["--no-wait", "if set, will not wait for target node to terminate"]
+ ]
+ end
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Stops RabbitMQ and its runtime (Erlang VM). Monitors progress for local nodes. Does not require a PID file path."
+
+ def banner(_, _), do: nil
+
+
+ #
+ # Implementation
+ #
+
+ def addressing_local_node?(_, remote_hostname) when remote_hostname == :localhost , do: :true
+ def addressing_local_node?(_, remote_hostname) when remote_hostname == 'localhost', do: :true
+ def addressing_local_node?(_, remote_hostname) when remote_hostname == "localhost", do: :true
+ def addressing_local_node?(local_hostname, remote_hostname) do
+ local_hostname == remote_hostname
+ end
+
+ defp shut_down_node_without_waiting(node_name, timeout) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :stop_and_halt, [], timeout)
+ end
+
+ defp shut_down_node_and_wait_pid_to_stop(node_name, pid, timeout) do
+ {:stream,
+ RabbitMQ.CLI.Core.Helpers.stream_until_error([
+ fn -> "Shutting down RabbitMQ node #{node_name} running at PID #{pid}" end,
+ fn ->
+ res = shut_down_node_without_waiting(node_name, timeout)
+
+ case res do
+ :ok -> "Waiting for PID #{pid} to terminate"
+ {:badrpc, err} -> {:error, err}
+ {:error, _} = err -> err
+ end
+ end,
+ fn ->
+ OsPid.wait_for_os_process_death(pid)
+ "RabbitMQ node #{node_name} running at PID #{pid} successfully shut down"
+ end
+ ])}
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/start_app_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/start_app_command.ex
new file mode 100644
index 0000000000..900bd762fa
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/start_app_command.ex
@@ -0,0 +1,25 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.StartAppCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :start, [])
+ end
+
+ def usage, do: "start_app"
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Starts the RabbitMQ application but leaves the runtime (Erlang VM) running"
+
+ def banner(_, %{node: node_name}), do: "Starting node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/status_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/status_command.ex
new file mode 100644
index 0000000000..582f514f27
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/status_command.ex
@@ -0,0 +1,253 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.StatusCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ alias RabbitMQ.CLI.InformationUnit, as: IU
+ import RabbitMQ.CLI.Core.{Alarms, ANSI, DataCoercion, Listeners, Memory, Platform}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 60_000
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ def switches(), do: [unit: :string, timeout: :integer]
+ def aliases(), do: [t: :timeout]
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(%{unit: "gb", timeout: timeout}, opts)}
+ end
+
+ def validate(args, _) when length(args) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate(_, %{unit: unit}) do
+ case IU.known_unit?(unit) do
+ true ->
+ :ok
+
+ false ->
+ {:validation_failure, "unit '#{unit}' is not supported. Please use one of: bytes, mb, gb"}
+ end
+ end
+ def validate(_, _), do: :ok
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :status, [], timeout)
+ end
+
+ def output({:error, :timeout}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: timed out while waiting for a response from #{node_name}."}
+ end
+
+ def output(result, %{formatter: "erlang"}) do
+ {:ok, result}
+ end
+
+ def output(result, %{formatter: "json"}) when is_list(result) do
+ m = result_map(result) |> Map.update(:alarms, [], fn xs -> alarm_maps(xs) end)
+
+ {:ok, m}
+ end
+
+ def output(result, %{node: node_name, unit: unit}) when is_list(result) do
+ m = result_map(result)
+
+ product_name_section = case m do
+ %{:product_name => product_name} when product_name != "" ->
+ ["Product name: #{product_name}"]
+ _ ->
+ []
+ end
+ product_version_section = case m do
+ %{:product_version => product_version} when product_version != "" ->
+ ["Product version: #{product_version}"]
+ _ ->
+ []
+ end
+
+ runtime_section = [
+ "#{bright("Runtime")}\n",
+ "OS PID: #{m[:pid]}",
+ "OS: #{m[:os]}",
+ # TODO: format
+ "Uptime (seconds): #{m[:uptime]}",
+ "Is under maintenance?: #{m[:is_under_maintenance]}"
+ ] ++
+ product_name_section ++
+ product_version_section ++
+ [
+ "RabbitMQ version: #{m[:rabbitmq_version]}",
+ "Node name: #{node_name}",
+ "Erlang configuration: #{m[:erlang_version]}",
+ "Erlang processes: #{m[:processes][:used]} used, #{m[:processes][:limit]} limit",
+ "Scheduler run queue: #{m[:run_queue]}",
+ "Cluster heartbeat timeout (net_ticktime): #{m[:net_ticktime]}"
+ ]
+
+ plugin_section = [
+ "\n#{bright("Plugins")}\n",
+ "Enabled plugin file: #{m[:enabled_plugin_file]}",
+ "Enabled plugins:\n"
+ ] ++ Enum.map(m[:active_plugins], fn pl -> " * #{pl}" end)
+
+ data_directory_section = [
+ "\n#{bright("Data directory")}\n",
+ "Node data directory: #{m[:data_directory]}",
+ "Raft data directory: #{m[:raft_data_directory]}"
+ ]
+
+ config_section = [
+ "\n#{bright("Config files")}\n"
+ ] ++ Enum.map(m[:config_files], fn path -> " * #{path}" end)
+
+ log_section = [
+ "\n#{bright("Log file(s)")}\n"
+ ] ++ Enum.map(m[:log_files], fn path -> " * #{path}" end)
+
+ alarms_section = [
+ "\n#{bright("Alarms")}\n",
+ ] ++ case m[:alarms] do
+ [] -> ["(none)"]
+ xs -> alarm_lines(xs, node_name)
+ end
+
+ breakdown = compute_relative_values(m[:memory])
+ memory_calculation_strategy = to_atom(m[:vm_memory_calculation_strategy])
+ total_memory = get_in(m[:memory], [:total, memory_calculation_strategy])
+
+ readable_watermark_setting = case m[:vm_memory_high_watermark_setting] do
+ %{:relative => val} -> "#{val} of available memory"
+ # absolute value
+ %{:absolute => val} -> "#{IU.convert(val, unit)} #{unit}"
+ end
+ memory_section = [
+ "\n#{bright("Memory")}\n",
+ "Total memory used: #{IU.convert(total_memory, unit)} #{unit}",
+ "Calculation strategy: #{memory_calculation_strategy}",
+ "Memory high watermark setting: #{readable_watermark_setting}, computed to: #{IU.convert(m[:vm_memory_high_watermark_limit], unit)} #{unit}\n"
+ ] ++ Enum.map(breakdown, fn({category, val}) -> "#{category}: #{IU.convert(val[:bytes], unit)} #{unit} (#{val[:percentage]} %)" end)
+
+ file_descriptors = [
+ "\n#{bright("File Descriptors")}\n",
+ "Total: #{m[:file_descriptors][:total_used]}, limit: #{m[:file_descriptors][:total_limit]}",
+ "Sockets: #{m[:file_descriptors][:sockets_used]}, limit: #{m[:file_descriptors][:sockets_limit]}"
+ ]
+
+ disk_space_section = [
+ "\n#{bright("Free Disk Space")}\n",
+ "Low free disk space watermark: #{IU.convert(m[:disk_free_limit], unit)} #{unit}",
+ "Free disk space: #{IU.convert(m[:disk_free], unit)} #{unit}"
+ ]
+
+ totals_section = [
+ "\n#{bright("Totals")}\n",
+ "Connection count: #{m[:totals][:connection_count]}",
+ "Queue count: #{m[:totals][:queue_count]}",
+ "Virtual host count: #{m[:totals][:virtual_host_count]}"
+ ]
+
+ listeners_section = [
+ "\n#{bright("Listeners")}\n",
+ ] ++ case m[:listeners] do
+ [] -> ["(none)"]
+ xs -> listener_lines(xs)
+ end
+ lines = runtime_section ++ plugin_section ++ data_directory_section ++
+ config_section ++ log_section ++ alarms_section ++ memory_section ++
+ file_descriptors ++ disk_space_section ++ totals_section ++ listeners_section
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.String
+
+ def usage, do: "status [--unit <unit>]"
+
+ def usage_additional() do
+ [
+ ["--unit <bytes | mb | gb>", "byte multiple (bytes, megabytes, gigabytes) to use"],
+ ["--formatter <json | erlang>", "alternative formatter (JSON, Erlang terms)"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Displays status of a node"
+
+ def banner(_, %{node: node_name}), do: "Status of node #{node_name} ..."
+
+ #
+ # Implementation
+ #
+
+ defp result_map(result) do
+ %{
+ os: os_name(Keyword.get(result, :os)),
+ pid: Keyword.get(result, :pid),
+ product_name: Keyword.get(result, :product_name) |> to_string,
+ product_version: Keyword.get(result, :product_version) |> to_string,
+ rabbitmq_version: Keyword.get(result, :rabbitmq_version) |> to_string,
+ erlang_version: Keyword.get(result, :erlang_version) |> to_string |> String.trim_trailing,
+ uptime: Keyword.get(result, :uptime),
+ is_under_maintenance: Keyword.get(result, :is_under_maintenance, false),
+ processes: Enum.into(Keyword.get(result, :processes), %{}),
+ run_queue: Keyword.get(result, :run_queue),
+ net_ticktime: net_ticktime(result),
+
+ vm_memory_calculation_strategy: Keyword.get(result, :vm_memory_calculation_strategy),
+ vm_memory_high_watermark_setting: Keyword.get(result, :vm_memory_high_watermark) |> formatted_watermark,
+ vm_memory_high_watermark_limit: Keyword.get(result, :vm_memory_limit),
+
+ disk_free_limit: Keyword.get(result, :disk_free_limit),
+ disk_free: Keyword.get(result, :disk_free),
+
+ file_descriptors: Enum.into(Keyword.get(result, :file_descriptors), %{}),
+
+ alarms: Keyword.get(result, :alarms),
+ listeners: listener_maps(Keyword.get(result, :listeners, [])),
+ memory: Keyword.get(result, :memory) |> Enum.into(%{}),
+
+ data_directory: Keyword.get(result, :data_directory) |> to_string,
+ raft_data_directory: Keyword.get(result, :raft_data_directory) |> to_string,
+
+ config_files: Keyword.get(result, :config_files) |> Enum.map(&to_string/1),
+ log_files: Keyword.get(result, :log_files) |> Enum.map(&to_string/1),
+
+ active_plugins: Keyword.get(result, :active_plugins) |> Enum.map(&to_string/1),
+ enabled_plugin_file: Keyword.get(result, :enabled_plugin_file) |> to_string,
+
+ totals: Keyword.get(result, :totals)
+ }
+ end
+
+ defp net_ticktime(result) do
+ case Keyword.get(result, :kernel) do
+ {:net_ticktime, n} -> n
+ n when is_integer(n) -> n
+ _ -> :undefined
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_app_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_app_command.ex
new file mode 100644
index 0000000000..c3cbe3b1fd
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_app_command.ex
@@ -0,0 +1,26 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.StopAppCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :stop, [])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "stop_app"
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Stops the RabbitMQ application, leaving the runtime (Erlang VM) running"
+
+ def banner(_, %{node: node_name}), do: "Stopping rabbit application on node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_command.ex
new file mode 100644
index 0000000000..becb75a0b5
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/stop_command.ex
@@ -0,0 +1,72 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.StopCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ alias RabbitMQ.CLI.Core.OsPid
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{idempotent: false}, opts)}
+ end
+
+ def switches(), do: [idempotent: :boolean]
+
+ def validate([], _), do: :ok
+ def validate([_pidfile_path], _), do: :ok
+ def validate([_ | _] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
+
+ def run([], %{node: node_name, idempotent: true}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit, :stop_and_halt, []) do
+ {:badrpc, :nodedown} -> {:ok, "Node #{node_name} is no longer running"}
+ any -> any
+ end
+ end
+
+ def run([], %{node: node_name, idempotent: false}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :stop_and_halt, [])
+ end
+
+ def run([pidfile_path], %{node: node_name}) do
+ ret = OsPid.read_pid_from_file(pidfile_path, true)
+ :rabbit_misc.rpc_call(node_name, :rabbit, :stop_and_halt, [])
+
+ case ret do
+ {:error, details} ->
+ {:error, "could not read pid from file #{pidfile_path}. Error: #{details}"}
+
+ {:error, :could_not_read_pid_from_file, {:contents, s}} ->
+ {:error, "could not read pid from file #{pidfile_path}. File contents: #{s}"}
+
+ {:error, :could_not_read_pid_from_file, details} ->
+ {:error, "could not read pid from file #{pidfile_path}. Error: #{details}"}
+
+ pid ->
+ OsPid.wait_for_os_process_death(pid)
+ {:ok, "process #{pid} (take from pid file #{pidfile_path}) is no longer running"}
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "stop [--idempotent] [<pidfile>]"
+
+ def usage_additional() do
+ [
+ ["<pidfile>", "node PID file path to monitor. To avoid using a PID file, use 'rabbitmqctl shutdown'"],
+ ["--idempotent", "return success if target node is not running (cannot be contacted)"]
+ ]
+ end
+
+ def description(), do: "Stops RabbitMQ and its runtime (Erlang VM). Requires a local node pid file path to monitor progress."
+
+ def help_section(), do: :node_management
+
+ def banner([pidfile_path], %{node: node_name}) do
+ "Stopping and halting node #{node_name} (will monitor pid file #{pidfile_path}) ..."
+ end
+
+ def banner(_, %{node: node_name}), do: "Stopping and halting node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/suspend_listeners_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/suspend_listeners_command.ex
new file mode 100644
index 0000000000..31fcf738b9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/suspend_listeners_command.ex
@@ -0,0 +1,46 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SuspendListenersCommand do
+ @moduledoc """
+ Suspends all client connection listeners. Suspended listeners will not
+ accept any new connections but already established ones will not be interrupted.
+ `ResumeListenersCommand` will undo the effect of this command.
+
+ This command is meant to be used when automating upgrades.
+ See also `ResumeListenersCommand`.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_maintenance, :suspend_all_client_listeners, [], timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "suspend_listeners"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Suspends client connection listeners so that no new client connections are accepted"
+
+ def banner(_, %{node: node_name}) do
+ "Will suspend client connection listeners on node #{node_name}. "
+ <> "The node will no longer accept client connections!"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/sync_queue_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/sync_queue_command.ex
new file mode 100644
index 0000000000..4b7112af57
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/sync_queue_command.ex
@@ -0,0 +1,54 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.SyncQueueCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([queue], %{vhost: vhost, node: node_name}) do
+ :rpc.call(
+ node_name,
+ :rabbit_mirror_queue_misc,
+ :sync_queue,
+ [:rabbit_misc.r(vhost, :queue, queue)],
+ :infinity
+ )
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage do
+ "sync_queue [--vhost <vhost>] <queue>"
+ end
+
+ def usage_additional() do
+ [
+ ["<queue>", "Name of the queue to synchronise"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.mirroring()
+ ]
+ end
+
+ def help_section(), do: :replication
+
+ def description(), do: "Instructs a mirrored queue with unsynchronised mirrors (follower replicas) to synchronise them"
+
+ def banner([queue], %{vhost: vhost, node: _node}) do
+ "Synchronising queue '#{queue}' in vhost '#{vhost}' ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_off_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_off_command.ex
new file mode 100644
index 0000000000..f2b6cc217f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_off_command.ex
@@ -0,0 +1,42 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.TraceOffCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(_, opts) do
+ {[], Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, vhost: vhost}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_trace, :stop, [vhost]) do
+ :ok -> {:ok, "Trace disabled for vhost #{vhost}"}
+ other -> other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage do
+ "trace_off [--vhost <vhost>]"
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.firehose(),
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def banner(_, %{vhost: vhost}), do: "Stopping tracing for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_on_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_on_command.ex
new file mode 100644
index 0000000000..33bb5a06d6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/trace_on_command.ex
@@ -0,0 +1,42 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.TraceOnCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(_, opts) do
+ {[], Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, vhost: vhost}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_trace, :start, [vhost]) do
+ :ok -> {:ok, "Trace enabled for vhost #{vhost}"}
+ other -> other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage do
+ "trace_on [--vhost <vhost>]"
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.firehose(),
+ DocGuide.virtual_hosts()
+ ]
+ end
+
+ def help_section(), do: :virtual_hosts
+
+ def banner(_, %{vhost: vhost}), do: "Starting tracing for vhost \"#{vhost}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/update_cluster_nodes_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/update_cluster_nodes_command.ex
new file mode 100644
index 0000000000..94b218e2c9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/update_cluster_nodes_command.ex
@@ -0,0 +1,62 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.UpdateClusterNodesCommand do
+ alias RabbitMQ.CLI.Core.{Config, DocGuide, Helpers}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppStopped
+
+ def run([seed_node], options=%{node: node_name}) do
+ long_or_short_names = Config.get_option(:longnames, options)
+ seed_node_normalised = Helpers.normalise_node(seed_node, long_or_short_names)
+ :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_mnesia,
+ :update_cluster_nodes,
+ [seed_node_normalised]
+ )
+ end
+
+ def usage() do
+ "update_cluster_nodes <seed_node>"
+ end
+
+ def usage_additional() do
+ [
+ ["<seed_node>", "Cluster node to seed known cluster members from"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.clustering()
+ ]
+ end
+
+ def help_section(), do: :cluster_management
+
+ def description(), do: "Instructs a cluster member node to sync the list of known cluster members from <seed_node>"
+
+ def banner([seed_node], %{node: node_name}) do
+ "Will seed #{node_name} from #{seed_node} on next start"
+ end
+
+ def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
+ end
+
+ def output({:error, :cannot_cluster_node_with_itself}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(),
+ "Error: cannot cluster node with itself: #{node_name}"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/version_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/version_command.ex
new file mode 100644
index 0000000000..8028054932
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/version_command.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.VersionCommand do
+ alias RabbitMQ.CLI.Core.{Validators, Version}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:ctl, :diagnostics, :plugins]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def validate_execution_environment([] = args, opts) do
+ Validators.rabbit_is_loaded(args, opts)
+ end
+
+ def run([], %{formatter: "json"}) do
+ {:ok, %{version: Version.local_version()}}
+ end
+ def run([], %{formatter: "csv"}) do
+ row = [version: Version.local_version()]
+ {:ok, [row]}
+ end
+ def run([], _opts) do
+ {:ok, Version.local_version()}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section, do: :help
+
+ def description, do: "Displays CLI tools version"
+
+ def usage, do: "version"
+
+ def banner(_, _), do: nil
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/wait_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/wait_command.ex
new file mode 100644
index 0000000000..0699203de6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/wait_command.ex
@@ -0,0 +1,269 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.Commands.WaitCommand do
+ alias RabbitMQ.CLI.Core.{Helpers, Validators}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ @default_timeout 10_000
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ def switches(), do: [pid: :integer, timeout: :integer]
+ def aliases(), do: [P: :pid, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ val -> val
+ end
+
+ {args, Map.put(opts, :timeout, timeout)}
+ end
+
+ def validate([_ | _] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
+ def validate([_], %{pid: _}), do: {:validation_failure, "Cannot specify both pid and pidfile"}
+ def validate([_], _), do: :ok
+ def validate([], %{pid: _}), do: :ok
+ def validate([], _), do: {:validation_failure, "No pid or pidfile specified"}
+
+ def validate_execution_environment([], %{pid: _} = opts) do
+ Validators.rabbit_is_loaded([], opts)
+ end
+ def validate_execution_environment([_pid_file], opts) do
+ Validators.rabbit_is_loaded([], opts)
+ end
+
+ def run([pid_file], %{node: node_name, timeout: timeout} = opts) do
+ app_names = :rabbit_and_plugins
+ quiet = opts[:quiet] || false
+
+ Helpers.stream_until_error_parameterised(
+ [
+ log("Waiting for pid file '#{pid_file}' to appear", quiet),
+ fn _ -> wait_for_pid_file(pid_file, node_name, timeout) end,
+ log_param(fn pid -> "pid is #{pid}" end, quiet)
+ ] ++
+ wait_for_pid_funs(node_name, app_names, timeout, quiet),
+ :init
+ )
+ end
+
+ def run([], %{node: node_name, pid: pid, timeout: timeout} = opts) do
+ app_names = :rabbit_and_plugins
+ quiet = opts[:quiet] || false
+
+ Helpers.stream_until_error_parameterised(
+ wait_for_pid_funs(node_name, app_names, timeout, quiet),
+ pid
+ )
+ end
+
+ def output({:error, err}, opts) do
+ case format_error(err) do
+ :undefined -> RabbitMQ.CLI.DefaultOutput.output({:error, err}, opts)
+ error_str -> {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(), error_str}
+ end
+ end
+
+ def output({:stream, stream}, _opts) do
+ {:stream,
+ Stream.map(stream, fn
+ {:error, err} ->
+ {:error,
+ case format_error(err) do
+ :undefined -> err
+ error_str -> error_str
+ end}
+
+ other ->
+ other
+ end)}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ # Banner is printed in wait steps
+ def banner(_, _), do: nil
+
+ def usage, do: "wait [<pidfile>] [--pid|-P <pid>]"
+
+ def usage_additional() do
+ [
+ ["<pidfile>", "PID file path"],
+ ["--pid <pid>", "operating system PID to monitor"]
+ ]
+ end
+
+ def help_section(), do: :node_management
+
+ def description(), do: "Waits for RabbitMQ node startup by monitoring a local PID file. See also 'rabbitmqctl await_online_nodes'"
+
+ #
+ # Implementation
+ #
+
+ def wait_for(timeout, fun) do
+ sleep = 1000
+
+ case wait_for_loop(timeout, sleep, fun) do
+ {:error, :timeout} -> {:error, {:timeout, timeout}}
+ other -> other
+ end
+ end
+
+ def wait_for_loop(timeout, _, _) when timeout <= 0 do
+ {:error, :timeout}
+ end
+
+ def wait_for_loop(timeout, sleep, fun) do
+ time = :erlang.system_time(:milli_seconds)
+
+ case fun.() do
+ {:error, :loop} ->
+ time_to_fun = :erlang.system_time(:milli_seconds) - time
+
+ time_taken =
+ case {time_to_fun > timeout, time_to_fun > sleep} do
+ ## The function took longer than timeout
+ {true, _} ->
+ time_to_fun
+
+ ## The function took longer than sleep
+ {false, true} ->
+ time_to_fun
+
+ ## We need to sleep
+ {false, false} ->
+ :timer.sleep(sleep)
+ time_to_fun + sleep
+ end
+
+ wait_for_loop(timeout - time_taken, sleep, fun)
+
+ other ->
+ other
+ end
+ end
+
+ defp wait_for_pid_funs(node_name, app_names, timeout, quiet) do
+ app_names_formatted = :io_lib.format('~p', [app_names])
+
+ [
+ log_param(
+ fn pid ->
+ "Waiting for erlang distribution on node '#{node_name}' while OS process '#{pid}' is running"
+ end,
+ quiet
+ ),
+ fn pid -> wait_for_erlang_distribution(pid, node_name, timeout) end,
+ log(
+ "Waiting for applications '#{app_names_formatted}' to start on node '#{node_name}'",
+ quiet
+ ),
+ fn _ -> wait_for_application(node_name, app_names) end,
+ log("Applications '#{app_names_formatted}' are running on node '#{node_name}'", quiet)
+ ]
+ end
+
+ defp log(_string, _quiet = true) do
+ fn val -> {:ok, val} end
+ end
+
+ defp log(string, _quiet = false) do
+ fn val -> {:ok, val, string} end
+ end
+
+ defp log_param(_fun, _quiet = true) do
+ fn val -> {:ok, val} end
+ end
+
+ defp log_param(fun, _quiet = false) do
+ fn val -> {:ok, val, fun.(val)} end
+ end
+
+ defp format_error(:process_not_running) do
+ "Error: process is not running."
+ end
+
+ defp format_error({:garbage_in_pid_file, _}) do
+ "Error: garbage in pid file."
+ end
+
+ defp format_error({:could_not_read_pid, err}) do
+ "Error: could not read pid. Detail: #{err}"
+ end
+
+ defp format_error(_) do
+ :undefined
+ end
+
+ defp wait_for_application(node_name, :rabbit_and_plugins) do
+ case :rabbit.await_startup(node_name) do
+ {:badrpc, err} -> {:error, {:badrpc, err}}
+ other -> other
+ end
+ end
+
+ defp wait_for_erlang_distribution(pid, node_name, timeout) do
+ wait_for(
+ timeout,
+ fn ->
+ case check_distribution(pid, node_name) do
+ # Loop while node is available.
+ {:error, :pang} -> {:error, :loop}
+ other -> other
+ end
+ end
+ )
+ end
+
+ defp check_distribution(pid, node_name) do
+ case is_os_process_alive(pid) do
+ true ->
+ case Node.ping(node_name) do
+ :pong -> :ok
+ :pang -> {:error, :pang}
+ end
+
+ false ->
+ {:error, :process_not_running}
+ end
+ end
+
+ defp is_os_process_alive(pid) do
+ :rabbit_misc.is_os_process_alive(to_charlist(pid))
+ end
+
+ defp wait_for_pid_file(pid_file, node_name, timeout) do
+ wait_for(
+ timeout,
+ fn ->
+ case :file.read_file(pid_file) do
+ {:ok, bin} ->
+ case Integer.parse(bin) do
+ :error ->
+ {:error, {:garbage_in_pid_file, pid_file}}
+
+ {pid, _} ->
+ case check_distribution(pid, node_name) do
+ :ok -> {:ok, pid}
+ _ -> {:error, :loop}
+ end
+ end
+
+ {:error, :enoent} ->
+ {:error, :loop}
+
+ {:error, err} ->
+ {:error, {:could_not_read_pid, err}}
+ end
+ end
+ )
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex
new file mode 100644
index 0000000000..26f86ae51e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex
@@ -0,0 +1,62 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.InfoKeys do
+ import RabbitCommon.Records
+ alias RabbitMQ.CLI.Core.DataCoercion
+
+ def validate_info_keys(args, valid_keys) do
+ info_keys = prepare_info_keys(args)
+
+ case invalid_info_keys(info_keys, Enum.map(valid_keys, &DataCoercion.to_atom/1)) do
+ [_ | _] = bad_info_keys ->
+ {:validation_failure, {:bad_info_key, bad_info_keys}}
+
+ [] ->
+ {:ok, info_keys}
+ end
+ end
+
+ def prepare_info_keys(args) do
+ args
+ |> Enum.flat_map(fn arg -> String.split(arg, ",", trim: true) end)
+ |> Enum.map(fn s -> String.replace(s, ",", "") end)
+ |> Enum.map(&String.trim/1)
+ |> Enum.map(&String.to_atom/1)
+ |> Enum.uniq()
+ end
+
+ def with_valid_info_keys(args, valid_keys, fun) do
+ case validate_info_keys(args, valid_keys) do
+ {:ok, info_keys} -> fun.(info_keys)
+ err -> err
+ end
+ end
+
+ defp invalid_info_keys(info_keys, valid_keys) do
+ MapSet.new(info_keys)
+ |> MapSet.difference(MapSet.new(valid_keys))
+ |> MapSet.to_list()
+ end
+
+ def info_for_keys(item, []) do
+ item
+ end
+
+ def info_for_keys([{_, _} | _] = item, info_keys) do
+ item
+ |> Enum.filter(fn {k, _} -> Enum.member?(info_keys, k) end)
+ |> Enum.map(fn {k, v} -> {k, format_info_item(v)} end)
+ end
+
+ defp format_info_item(resource(name: name)) do
+ name
+ end
+
+ defp format_info_item(any) do
+ any
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/rpc_stream.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/rpc_stream.ex
new file mode 100644
index 0000000000..4b672a6d88
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/rpc_stream.ex
@@ -0,0 +1,124 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Ctl.RpcStream do
+ alias RabbitMQ.CLI.Ctl.InfoKeys
+
+ def receive_list_items(node, mod, fun, args, timeout, info_keys) do
+ receive_list_items(node, [{mod, fun, args}], timeout, info_keys, 1)
+ end
+
+ def receive_list_items(node, mod, fun, args, timeout, info_keys, chunks) do
+ receive_list_items(node, [{mod, fun, args}], timeout, info_keys, chunks)
+ end
+
+ def receive_list_items(_node, _mfas, _timeout, _info_keys, 0) do
+ nil
+ end
+
+ def receive_list_items(node, mfas, timeout, info_keys, chunks_init) do
+ receive_list_items_with_fun(node, mfas, timeout, info_keys, chunks_init, fn v -> v end)
+ end
+
+ def receive_list_items_with_fun(node, mfas, timeout, info_keys, chunks_init, response_fun) do
+ pid = Kernel.self()
+ ref = Kernel.make_ref()
+ for {m, f, a} <- mfas, do: init_items_stream(node, m, f, a, timeout, pid, ref)
+
+ Stream.unfold(
+ {chunks_init, :continue},
+ fn
+ :finished ->
+ response_fun.(nil)
+
+ {chunks, :continue} ->
+ received =
+ receive do
+ {^ref, :finished} when chunks === 1 ->
+ nil
+
+ {^ref, :finished} ->
+ {[], {chunks - 1, :continue}}
+
+ {^ref, {:timeout, t}} ->
+ {{:error, {:badrpc, {:timeout, t / 1000}}}, :finished}
+
+ {^ref, []} ->
+ {[], {chunks, :continue}}
+
+ {^ref, :error, {:badrpc, :timeout}} ->
+ {{:error, {:badrpc, {:timeout, timeout / 1000}}}, :finished}
+
+ {^ref, result, :continue} ->
+ {result, {chunks, :continue}}
+
+ {:error, _} = error ->
+ {error, :finished}
+
+ {^ref, :error, error} ->
+ {{:error, simplify_emission_error(error)}, :finished}
+
+ {:DOWN, _mref, :process, _pid, :normal} ->
+ {[], {chunks, :continue}}
+
+ {:DOWN, _mref, :process, _pid, reason} ->
+ {{:error, simplify_emission_error(reason)}, :finished}
+ end
+
+ response_fun.(received)
+ end
+ )
+ |> display_list_items(info_keys)
+ end
+
+ def simplify_emission_error({:badrpc, {:EXIT, {{:nocatch, error}, error_details}}}) do
+ {error, error_details}
+ end
+
+ def simplify_emission_error({{:nocatch, error}, error_details}) do
+ {error, error_details}
+ end
+
+ def simplify_emission_error(other) do
+ other
+ end
+
+ defp display_list_items(items, info_keys) do
+ items
+ |> Stream.filter(fn
+ [] -> false
+ _ -> true
+ end)
+ |> Stream.map(fn
+ {:error, error} ->
+ error
+
+ # here item is a list of keyword lists:
+ [[{_, _} | _] | _] = item ->
+ Enum.map(item, fn i -> InfoKeys.info_for_keys(i, info_keys) end)
+
+ item ->
+ InfoKeys.info_for_keys(item, info_keys)
+ end)
+ end
+
+ defp init_items_stream(_node, _mod, _fun, _args, 0, pid, ref) do
+ set_stream_timeout(pid, ref, 0)
+ end
+
+ defp init_items_stream(node, mod, fun, args, timeout, pid, ref) do
+ :rabbit_control_misc.spawn_emitter_caller(node, mod, fun, args, ref, pid, timeout)
+ set_stream_timeout(pid, ref, timeout)
+ end
+
+ defp set_stream_timeout(_, _, :infinity) do
+ :ok
+ end
+
+ defp set_stream_timeout(pid, ref, timeout) do
+ Process.send_after(pid, {ref, {:timeout, timeout}}, timeout)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/default_output.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/default_output.ex
new file mode 100644
index 0000000000..d5e3f94a15
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/default_output.ex
@@ -0,0 +1,94 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.DefaultOutput do
+ # When `use RabbitMQ.CLI.DefaultOutput` is invoked,
+ # this will define output/2 that delegates to RabbitMQ.CLI.DefaultOutput.output/2.
+ defmacro __using__(_) do
+ quote do
+ def output(result, opts) do
+ RabbitMQ.CLI.DefaultOutput.output(result, opts)
+ end
+ end
+ end
+
+ def output(result, opts \\ %{}) do
+ format_output(normalize_output(result, opts))
+ end
+
+ def mnesia_running_error(node_name) do
+ "Mnesia is still running on node #{node_name}.\n" <>
+ "Please stop RabbitMQ with 'rabbitmqctl stop_app' first."
+ end
+
+ defp normalize_output(:ok, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name}}
+ end
+ defp normalize_output(:ok, _opts), do: :ok
+ defp normalize_output({:ok, value}, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "value" => value}}
+ end
+ defp normalize_output({:ok, _} = input, _opts), do: input
+ defp normalize_output({:stream, _} = input, _opts), do: input
+ defp normalize_output({:badrpc_multi, _, _} = input, _opts), do: {:error, input}
+ defp normalize_output({:badrpc, :nodedown} = input, _opts), do: {:error, input}
+ defp normalize_output({:badrpc, :timeout} = input, _opts), do: {:error, input}
+ defp normalize_output({:badrpc, {:timeout, _n}} = input, _opts), do: {:error, input}
+ defp normalize_output({:badrpc, {:timeout, _n, _msg}} = input, _opts), do: {:error, input}
+ defp normalize_output({:badrpc, {:EXIT, reason}}, _opts), do: {:error, reason}
+ defp normalize_output({:error, exit_code, string}, _opts) when is_integer(exit_code) do
+ {:error, exit_code, to_string(string)}
+ end
+ defp normalize_output({:error, format, args}, _opts)
+ when (is_list(format) or is_binary(format)) and is_list(args) do
+ {:error, to_string(:rabbit_misc.format(format, args))}
+ end
+ defp normalize_output({:error, _} = input, _opts), do: input
+ defp normalize_output({:error_string, string}, _opts) do
+ {:error, to_string(string)}
+ end
+ defp normalize_output(unknown, _opts) when is_atom(unknown), do: {:error, unknown}
+ defp normalize_output({unknown, _} = input, _opts) when is_atom(unknown), do: {:error, input}
+ defp normalize_output(result, _opts) when not is_atom(result), do: {:ok, result}
+
+
+ defp format_output({:error, _} = result) do
+ result
+ end
+ defp format_output({:error, _, _} = result) do
+ result
+ end
+
+ defp format_output(:ok) do
+ :ok
+ end
+
+ defp format_output({:ok, output}) do
+ case Enumerable.impl_for(output) do
+ nil ->
+ {:ok, output}
+
+ ## Do not streamify plain maps
+ Enumerable.Map ->
+ {:ok, output}
+
+ ## Do not streamify proplists
+ Enumerable.List ->
+ case FormatterHelpers.proplist?(output) do
+ true -> {:ok, output}
+ false -> {:stream, output}
+ end
+
+ _ ->
+ {:stream, output}
+ end
+ end
+
+ defp format_output({:stream, stream}) do
+ {:stream, stream}
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/alarms_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/alarms_command.ex
new file mode 100644
index 0000000000..7669a523eb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/alarms_command.ex
@@ -0,0 +1,77 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.AlarmsCommand do
+ @moduledoc """
+ Displays all alarms reported by the target node.
+
+ Returns a code of 0 unless there were connectivity and authentication
+ errors. This command is not meant to be used in health checks.
+ """
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+ import RabbitMQ.CLI.Core.Alarms
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Example response when there are alarms:
+ #
+ # [
+ # file_descriptor_limit,
+ # {{resource_limit,disk,hare@warp10},[]},
+ # {{resource_limit,memory,hare@warp10},[]},
+ # {{resource_limit,disk,rabbit@warp10},[]},
+ # {{resource_limit,memory,rabbit@warp10},[]}
+ # ]
+ #
+ # The topmost file_descriptor_limit alarm is node-local.
+ :rabbit_misc.rpc_call(node_name, :rabbit_alarm, :get_alarms, [], timeout)
+ end
+
+ def output([], %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "alarms" => []}}
+ end
+
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no alarms, local or clusterwide"}
+ end
+
+ def output(alarms, %{node: node_name, formatter: "json"}) do
+ local = local_alarms(alarms, node_name)
+ global = clusterwide_alarms(alarms, node_name)
+
+ {:ok,
+ %{
+ "result" => "ok",
+ "local" => alarm_lines(local, node_name),
+ "global" => alarm_lines(global, node_name),
+ "message" => "Node #{node_name} reported alarms"
+ }}
+ end
+
+ def output(alarms, %{node: node_name}) do
+ lines = alarm_lines(alarms, node_name)
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists resource alarms (local or cluster-wide) in effect on the target node"
+
+ def usage, do: "alarms"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} to report any known resource alarms ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/certificates_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/certificates_command.ex
new file mode 100644
index 0000000000..33320d8e37
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/certificates_command.ex
@@ -0,0 +1,55 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CertificatesCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.Listeners
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err ->
+ err
+
+ {:error, _, _} = err ->
+ err
+
+ xs when is_list(xs) ->
+ listeners = listeners_with_certificates(listeners_on(xs, node_name))
+
+ case listeners do
+ [] -> %{}
+ _ -> Enum.map(listeners, &listener_certs/1)
+ end
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "certificates"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.tls()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Displays certificates (public keys) for every listener on target node that is configured to use TLS"
+
+ def banner(_, %{node: node_name}), do: "Certificates of node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_alarms_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_alarms_command.ex
new file mode 100644
index 0000000000..04bb70317a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_alarms_command.ex
@@ -0,0 +1,86 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckAlarmsCommand do
+ @moduledoc """
+ Exits with a non-zero code if the target node reports any alarms,
+ local or clusterwide.
+
+ This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Alarms
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Example response when there are alarms:
+ #
+ # [
+ # file_descriptor_limit,
+ # {{resource_limit,disk,hare@warp10},[]},
+ # {{resource_limit,memory,hare@warp10},[]},
+ # {{resource_limit,disk,rabbit@warp10},[]},
+ # {{resource_limit,memory,rabbit@warp10},[]}
+ # ]
+ #
+ # The topmost file_descriptor_limit alarm is node-local.
+ :rabbit_misc.rpc_call(node_name, :rabbit_alarm, :get_alarms, [], timeout)
+ end
+
+ def output([], %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+
+ def output([], %{silent: true}) do
+ {:ok, :check_passed}
+ end
+
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no alarms, local or clusterwide"}
+ end
+
+ def output(alarms, %{node: node_name, formatter: "json"}) do
+ local = local_alarms(alarms, node_name)
+ global = clusterwide_alarms(alarms, node_name)
+
+ {:error, :check_failed,
+ %{
+ "result" => "error",
+ "local" => alarm_lines(local, node_name),
+ "global" => alarm_lines(global, node_name),
+ "message" => "Node #{node_name} reported alarms"
+ }}
+ end
+
+ def output(alarms, %{silent: true} = _opts) when is_list(alarms) do
+ {:error, :check_failed}
+ end
+
+ def output(alarms, %{node: node_name}) when is_list(alarms) do
+ lines = alarm_lines(alarms, node_name)
+
+ {:error, :check_failed, Enum.join(lines, line_separator())}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if the target node reports any alarms, local or cluster-wide."
+
+ def usage, do: "check_alarms"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} to report any local resource alarms ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration_command.ex
new file mode 100644
index 0000000000..d14ade59f6
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration_command.ex
@@ -0,0 +1,101 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckCertificateExpirationCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ alias RabbitMQ.CLI.TimeUnit, as: TU
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.Listeners
+
+ def switches(), do: [unit: :string, within: :integer]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{unit: "weeks", within: 4}, opts)}
+ end
+
+ def validate(args, _) when length(args) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate(_, %{unit: unit}) do
+ case TU.known_unit?(unit) do
+ true ->
+ :ok
+
+ false ->
+ {:validation_failure, "unit '#{unit}' is not supported. Please use one of: days, weeks, months, years"}
+ end
+ end
+ def validate(_, _), do: :ok
+
+ def run([], %{node: node_name, unit: unit, within: within, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err ->
+ err
+
+ {:error, _, _} = err ->
+ err
+
+ {:badrpc, _} = err ->
+ err
+
+ xs when is_list(xs) ->
+ listeners = listeners_on(xs, node_name)
+ seconds = TU.convert(within, unit)
+ Enum.reduce(listeners, [], fn (listener, acc) -> case listener_expiring_within(listener, seconds) do
+ false -> acc
+ expiring -> [expiring | acc]
+ end
+ end)
+ end
+ end
+
+ def output([], %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+
+ def output([], %{unit: unit, within: within}) do
+ unit_label = unit_label(within, unit)
+ {:ok, "No certificates are expiring within #{within} #{unit_label}."}
+ end
+
+ def output(listeners, %{formatter: "json"}) do
+ {:error, :check_failed, %{"result" => "error", "expired" => Enum.map(listeners, &expired_listener_map/1)}}
+ end
+
+ def output(listeners, %{}) do
+ {:error, :check_failed, Enum.map(listeners, &expired_listener_map/1)}
+ end
+
+ def unit_label(1, unit) do
+ unit |> String.slice(0..-2)
+ end
+ def unit_label(_within, unit) do
+ unit
+ end
+
+ def usage, do: "check_certificate_expiration [--within <period>] [--unit <unit>]"
+
+ def usage_additional() do
+ [
+ ["<period>", "period of time to check. Default is four (weeks)."],
+ ["<unit>", "time unit for the period, can be days, weeks, months, years. Default is weeks."],
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.tls(),
+ DocGuide.networking()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Checks the expiration date on the certificates for every listener configured to use TLS"
+
+ def banner(_, %{node: node_name}), do: "Checking certificate expiration on node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_local_alarms_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_local_alarms_command.ex
new file mode 100644
index 0000000000..1b11537793
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_local_alarms_command.ex
@@ -0,0 +1,85 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckLocalAlarmsCommand do
+ @moduledoc """
+ Exits with a non-zero code if the target node reports any local alarms.
+
+ This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Alarms
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Example response when there are alarms:
+ #
+ # [
+ # file_descriptor_limit,
+ # {{resource_limit,disk,hare@warp10},[]},
+ # {{resource_limit,memory,hare@warp10},[]},
+ # {{resource_limit,disk,rabbit@warp10},[]},
+ # {{resource_limit,memory,rabbit@warp10},[]}
+ # ]
+ #
+ # The topmost file_descriptor_limit alarm is node-local.
+ case :rabbit_misc.rpc_call(node_name, :rabbit_alarm, :get_alarms, [], timeout) do
+ [] -> []
+ xs when is_list(xs) -> local_alarms(xs, node_name)
+ other -> other
+ end
+ end
+
+ def output([], %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+
+ def output([], %{silent: true}) do
+ {:ok, :check_passed}
+ end
+
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no local alarms"}
+ end
+
+ def output(alarms, %{node: node_name, formatter: "json"}) do
+ {:error, :check_failed,
+ %{
+ "result" => "error",
+ "local" => alarm_lines(alarms, node_name),
+ "message" => "Node #{node_name} reported local alarms"
+ }}
+ end
+
+ def output(_alarms, %{silent: true}) do
+ {:error, :check_failed}
+ end
+
+ def output(alarms, %{node: node_name}) do
+ lines = alarm_lines(alarms, node_name)
+
+ {:error, Enum.join(lines, line_separator())}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if the target node reports any local alarms"
+
+ def usage, do: "check_local_alarms"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} to report any local resource alarms ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_connectivity_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_connectivity_command.ex
new file mode 100644
index 0000000000..1c3d86ed83
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_connectivity_command.ex
@@ -0,0 +1,119 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
+ @moduledoc """
+ Checks all listeners on the target node by opening a TCP connection to each
+ and immediately closing it.
+
+ Returns a code of 0 unless there were connectivity and authentication
+ errors. This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Diagnostics.Helpers,
+ only: [check_listener_connectivity: 3]
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+ import RabbitMQ.CLI.Core.Listeners
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 30_000
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+
+ {args, Map.merge(opts, %{timeout: timeout})}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err ->
+ err
+
+ {:error, _, _} = err ->
+ err
+
+ xs when is_list(xs) ->
+ locals = listeners_on(xs, node_name)
+
+ case locals do
+ [] -> {true, locals}
+ _ -> check_connectivity_of(locals, node_name, timeout)
+ end
+
+ other ->
+ other
+ end
+ end
+
+ def output({true, listeners}, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}}
+ end
+
+ def output({true, listeners}, %{node: node_name}) do
+ ports =
+ listeners
+ |> listener_maps
+ |> Enum.map(fn %{port: p} -> p end)
+ |> Enum.sort()
+ |> Enum.join(", ")
+
+ {:ok, "Successfully connected to ports #{ports} on node #{node_name}."}
+ end
+
+ def output({false, failures}, %{formatter: "json", node: node_name}) do
+ {:error, %{"result" => "error", "node" => node_name, "failures" => listener_maps(failures)}}
+ end
+
+ def output({false, failures}, %{node: node_name}) do
+ lines = [
+ "Connection to ports of the following listeners on node #{node_name} failed: "
+ | listener_lines(failures)
+ ]
+
+ {:error, Enum.join(lines, line_separator())}
+ end
+
+ def description(), do: "Basic TCP connectivity health check for each listener's port on the target node"
+
+ def help_section(), do: :observability_and_health_checks
+
+ def usage, do: "check_port_connectivity"
+
+ def banner([], %{node: node_name}) do
+ "Testing TCP connections to all active listeners on node #{node_name} ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp check_connectivity_of(listeners, node_name, timeout) do
+ # per listener timeout
+ t = Kernel.trunc(timeout / (length(listeners) + 1))
+
+ failures =
+ Enum.reject(
+ listeners,
+ fn l -> check_listener_connectivity(listener_map(l), node_name, t) end
+ )
+
+ case failures do
+ [] -> {true, listeners}
+ fs -> {false, fs}
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_listener_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_listener_command.ex
new file mode 100644
index 0000000000..f321d444db
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_port_listener_command.ex
@@ -0,0 +1,82 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortListenerCommand do
+ @moduledoc """
+ Exits with a non-zero code if there is no active listener
+ for the given port on the target node.
+
+ This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Listeners, only: [listeners_on: 2, listener_maps: 1]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositiveIntegerArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([port], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err ->
+ err
+
+ {:error, _, _} = err ->
+ err
+
+ xs when is_list(xs) ->
+ locals = listeners_on(xs, node_name) |> listener_maps
+
+ found =
+ Enum.any?(locals, fn %{port: p} ->
+ to_string(port) == to_string(p)
+ end)
+
+ case found do
+ true -> {true, port}
+ false -> {false, port, locals}
+ end
+
+ other ->
+ other
+ end
+ end
+
+ def output({true, port}, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "port" => port}}
+ end
+
+ def output({true, port}, %{node: node_name}) do
+ {:ok, "A listener for port #{port} is running on node #{node_name}."}
+ end
+
+ def output({false, port, listeners}, %{formatter: "json"}) do
+ ports = Enum.map(listeners, fn %{port: p} -> p end)
+
+ {:error, :check_failed,
+ %{"result" => "error", "missing" => port, "ports" => ports, "listeners" => listeners}}
+ end
+
+ def output({false, port, listeners}, %{node: node_name}) do
+ ports = Enum.map(listeners, fn %{port: p} -> p end) |> Enum.sort() |> Enum.join(", ")
+
+ {:error, :check_failed,
+ "No listener for port #{port} is active on node #{node_name}. " <>
+ "Found listeners that use the following ports: #{ports}"}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if target node does not have an active listener for given port"
+
+ def usage, do: "check_port_listener <port>"
+
+ def banner([port], %{node: node_name}) do
+ "Asking node #{node_name} if there's an active listener on port #{port} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_protocol_listener_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_protocol_listener_command.ex
new file mode 100644
index 0000000000..10c81c971e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_protocol_listener_command.ex
@@ -0,0 +1,90 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckProtocolListenerCommand do
+ @moduledoc """
+ Exits with a non-zero code if there is no active listener
+ for the given protocol on the target node.
+
+ This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Listeners,
+ only: [listeners_on: 2, listener_maps: 1, normalize_protocol: 1]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([proto], %{node: node_name, timeout: timeout}) do
+ proto = normalize_protocol(proto)
+
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err ->
+ err
+
+ {:error, _, _} = err ->
+ err
+
+ xs when is_list(xs) ->
+ locals = listeners_on(xs, node_name) |> listener_maps
+
+ found =
+ Enum.any?(locals, fn %{protocol: p} ->
+ to_string(proto) == to_string(p)
+ end)
+
+ case found do
+ true -> {true, proto}
+ false -> {false, proto, locals}
+ end
+
+ other ->
+ other
+ end
+ end
+
+ def output({true, proto}, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "protocol" => proto}}
+ end
+
+ def output({true, proto}, %{node: node_name}) do
+ {:ok, "A listener for protocol #{proto} is running on node #{node_name}."}
+ end
+
+ def output({false, proto, listeners}, %{formatter: "json"}) do
+ protocols = Enum.map(listeners, fn %{protocol: p} -> p end)
+
+ {:error,
+ %{
+ "result" => "error",
+ "missing" => proto,
+ "protocols" => protocols,
+ "listeners" => listeners
+ }}
+ end
+
+ def output({false, proto, listeners}, %{node: node_name}) do
+ protocols = Enum.map(listeners, fn %{protocol: p} -> p end) |> Enum.sort() |> Enum.join(", ")
+
+ {:error,
+ "No listener for protocol #{proto} is active on node #{node_name}. " <>
+ "Found listeners for the following protocols: #{protocols}"}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if target node does not have an active listener for given protocol"
+
+ def usage, do: "check_protocol_listener <protocol>"
+
+ def banner([proto], %{node: node_name}) do
+ "Asking node #{node_name} if there's an active listener for protocol #{proto} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_running_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_running_command.ex
new file mode 100644
index 0000000000..690f17e1e7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_running_command.ex
@@ -0,0 +1,46 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckRunningCommand do
+ @moduledoc """
+ Exits with a non-zero code if the RabbitMQ app on the target node is not running.
+
+ This command is meant to be used in health checks.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Note: we use is_booted/1 over is_running/1 to avoid
+ # returning a positive result when the node is still booting
+ :rabbit_misc.rpc_call(node_name, :rabbit, :is_booted, [node_name], timeout)
+ end
+
+ def output(true, %{node: node_name} = _options) do
+ {:ok, "RabbitMQ on node #{node_name} is fully booted and running"}
+ end
+
+ def output(false, %{node: node_name} = _options) do
+ {:error,
+ "RabbitMQ on node #{node_name} is not running or has not fully booted yet (check with is_booting)"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if the RabbitMQ app on the target node is not running"
+
+ def usage, do: "check_running"
+
+ def banner([], %{node: node_name}) do
+ "Checking if RabbitMQ is running on node #{node_name} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_virtual_hosts_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_virtual_hosts_command.ex
new file mode 100644
index 0000000000..b3169b522d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_virtual_hosts_command.ex
@@ -0,0 +1,71 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckVirtualHostsCommand do
+ @moduledoc """
+ Exits with a non-zero code if the target node reports any vhost down.
+
+ This command is meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vhost_sup_sup, :check, [], timeout)
+ end
+
+ def output([], %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+
+ def output([], %{silent: true}) do
+ {:ok, :check_passed}
+ end
+
+ def output([], %{formatter: "erlang"}) do
+ {:ok, :check_passed}
+ end
+
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported all vhosts as running"}
+ end
+
+ def output(vhosts, %{formatter: "erlang"} = _opts) when is_list(vhosts) do
+ {:error, :check_failed, {:down_vhosts, vhosts}}
+ end
+
+ def output(vhosts, %{formatter: "json"} = _opts) when is_list(vhosts) do
+ {:error, :check_failed, %{"result" => "error", "down_vhosts" => vhosts}}
+ end
+
+ def output(vhosts, %{silent: true} = _opts) when is_list(vhosts) do
+ {:error, :check_failed}
+ end
+
+ def output(vhosts, %{node: node_name}) when is_list(vhosts) do
+ lines = Enum.join(vhosts, line_separator())
+ {:error, "Some virtual hosts on node #{node_name} are down:\n#{lines}"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def description(), do: "Health check that checks if all vhosts are running in the target node"
+
+ def help_section(), do: :observability_and_health_checks
+
+ def usage, do: "check_virtual_hosts"
+
+ def banner([], %{node: node_name}) do
+ "Checking if all vhosts are running on node #{node_name} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/cipher_suites_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/cipher_suites_command.ex
new file mode 100644
index 0000000000..86e8eee3a4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/cipher_suites_command.ex
@@ -0,0 +1,122 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CipherSuitesCommand do
+ alias RabbitMQ.CLI.Core.Helpers
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{all: false, format: "openssl"}, Helpers.case_insensitive_format(opts))}
+ end
+
+ def switches(), do: [timeout: :integer,
+ format: :string,
+ all: :boolean]
+ def aliases(), do: [t: :timeout]
+
+ def validate(_, %{format: format})
+ when format != "openssl" and format != "erlang" and format != "map" do
+ {:validation_failure, {:bad_argument, "Format should be either openssl, erlang or map"}}
+ end
+ def validate(args, _) when length(args) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, _), do: :ok
+
+ def run([], %{node: node_name, timeout: timeout, format: format} = opts) do
+ {mod, function} = case format do
+ "openssl" -> {:rabbit_ssl, :cipher_suites_openssl};
+ "erlang" -> {:rabbit_ssl, :cipher_suites_erlang};
+ "map" -> {:rabbit_ssl, :cipher_suites}
+ end
+ args = case opts do
+ %{all: true} -> [:all];
+ %{} -> [:default]
+ end
+ :rabbit_misc.rpc_call(node_name, mod, function, args, timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([], %{format: "openssl"}), do: "Listing available cipher suites in OpenSSL format"
+ def banner([], %{format: "erlang"}), do: "Listing available cipher suites in Erlang term format"
+ def banner([], %{format: "map"}), do: "Listing available cipher suites in map format"
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists cipher suites enabled by default. To list all available cipher suites, add the --all argument."
+
+ def usage, do: "cipher_suites [--format <openssl | erlang | map>] [--all]"
+
+ def usage_additional() do
+ [
+ ["--format", "output format to use: openssl, erlang or map"],
+ ["--all", "list all available suites"]
+ ]
+ end
+
+ defmodule Formatter do
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(item, %{format: "erlang"}) do
+ to_string(:io_lib.format("~p", [item]))
+ end
+
+ def format_output(item, %{format: "map"}) do
+ to_string(:io_lib.format("~p", [item]))
+ end
+
+ def format_output(item, %{format: "openssl"} = opts) do
+ RabbitMQ.CLI.Formatters.String.format_output(item, opts)
+ end
+
+ def format_stream(stream, %{format: "erlang"} = opts) do
+ comma_separated(stream, opts)
+ end
+
+ def format_stream(stream, %{format: "map"} = opts) do
+ comma_separated(stream, opts)
+ end
+
+ def format_stream(stream, %{format: "openssl"} = opts) do
+ Stream.map(
+ stream,
+ FormatterHelpers.without_errors_1(fn el ->
+ format_output(el, opts)
+ end)
+ )
+ end
+
+ defp comma_separated(stream, opts) do
+ elements =
+ Stream.scan(
+ stream,
+ :empty,
+ FormatterHelpers.without_errors_2(fn element, previous ->
+ separator =
+ case previous do
+ :empty -> ""
+ _ -> ","
+ end
+
+ format_element(element, separator, opts)
+ end)
+ )
+
+ Stream.concat([["["], elements, ["]"]])
+ end
+
+ defp format_element(val, separator, opts) do
+ separator <> format_output(val, opts)
+ end
+ end
+
+ def formatter(), do: Formatter
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/command_line_arguments_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/command_line_arguments_command.ex
new file mode 100644
index 0000000000..adbf14cfc3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/command_line_arguments_command.ex
@@ -0,0 +1,41 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.CommandLineArgumentsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:diagnostics]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+
+ def validate(_, %{formatter: "json"}) do
+ {:validation_failure, :unsupported_formatter}
+ end
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :init, :get_arguments, [])
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Erlang
+
+ def usage, do: "command_line_arguments"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.configuration(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Displays target node's command-line arguments and flags as reported by the runtime"
+
+ def banner(_, %{node: node_name}), do: "Command line arguments of node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/consume_event_stream_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/consume_event_stream_command.ex
new file mode 100644
index 0000000000..e7ad171d11
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/consume_event_stream_command.ex
@@ -0,0 +1,71 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ConsumeEventStreamCommand do
+ @moduledoc """
+ Displays standard log file location on the target node
+ """
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [duration: :integer, pattern: :string, timeout: :integer]
+ def aliases(), do: [d: :duration, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{duration: :infinity, pattern: ".*", quiet: true}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout, duration: duration, pattern: pattern}) do
+ pid = self()
+ ref = make_ref()
+ subscribed = :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_event_consumer, :register,
+ [pid, ref, duration, pattern],
+ timeout)
+ case subscribed do
+ {:ok, ^ref} ->
+ Stream.unfold(:confinue,
+ fn(:finished) -> nil
+ (:confinue) ->
+ receive do
+ {^ref, data, :finished} ->
+ {data, :finished};
+ {^ref, data, :confinue} ->
+ {data, :confinue}
+ end
+ end)
+ error -> error
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.JsonStream
+
+ def printer(), do: RabbitMQ.CLI.Printers.StdIORaw
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Streams internal events from a running node. Output is jq-compatible."
+
+ def usage, do: "consume_event_stream [--duration|-d <seconds>] [--pattern <pattern>]"
+
+ def usage_additional() do
+ [
+ ["<duration_in_seconds>", "duration in seconds to stream log. Defaults to infinity"],
+ ["<pattern>", "regular expression to pick events"]
+ ]
+ end
+
+ def banner([], %{node: node_name, duration: :infinity}) do
+ "Streaming logs from node #{node_name} ..."
+ end
+ def banner([], %{node: node_name, duration: duration}) do
+ "Streaming logs from node #{node_name} for #{duration} seconds ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/disable_auth_attempt_source_tracking_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/disable_auth_attempt_source_tracking_command.ex
new file mode 100644
index 0000000000..df182a0c97
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/disable_auth_attempt_source_tracking_command.ex
@@ -0,0 +1,35 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.DisableAuthAttemptSourceTrackingCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :application, :set_env,
+ [:rabbit, :track_auth_attempt_source, :false])
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "disable_track_auth_attempt_source"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Disables the tracking of peer IP address and username of authentication attempts"
+
+ def banner([], _), do: "Disabling authentication attempt source tracking ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/discover_peers_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/discover_peers_command.ex
new file mode 100644
index 0000000000..b23a13e370
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/discover_peers_command.ex
@@ -0,0 +1,36 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.DiscoverPeersCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_peer_discovery, :discover_cluster_nodes, [], timeout)
+ end
+
+ def output({:ok, {[], _}}, _options) do
+ {:ok, "No peers discovered"}
+ end
+
+ def output({:ok, {nodes, _}}, _options) do
+ {:ok, nodes}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Performs peer discovery and lists discovered nodes, if any"
+
+ def usage, do: "discover_peers"
+
+ def banner(_, _), do: "Discovering peers nodes ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/enable_auth_attempt_source_tracking_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/enable_auth_attempt_source_tracking_command.ex
new file mode 100644
index 0000000000..832891094b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/enable_auth_attempt_source_tracking_command.ex
@@ -0,0 +1,36 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.EnableAuthAttemptSourceTrackingCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :application, :set_env,
+ [:rabbit, :track_auth_attempt_source, :true])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "enable_auth_attempt_source_tracking"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Enables the tracking of peer IP address and username of authentication attempts"
+
+ def banner([], _), do: "Enabling authentication attempt source tracking ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_hash_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_hash_command.ex
new file mode 100644
index 0000000000..b6e3186c94
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_hash_command.ex
@@ -0,0 +1,35 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ErlangCookieHashCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.rpc_call(node_name, :rabbit_nodes_common, :cookie_hash, [], timeout))
+ end
+
+ def output(result, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok", "value" => result}}
+ end
+ def output(result, _options) when is_bitstring(result) do
+ {:ok, result}
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Displays a hash of the Erlang cookie (shared secret) used by the target node"
+
+ def usage, do: "erlang_cookie_hash"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} its Erlang cookie hash..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_sources_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_sources_command.ex
new file mode 100644
index 0000000000..578ba31c73
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_cookie_sources_command.ex
@@ -0,0 +1,116 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ErlangCookieSourcesCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.ANSI
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def distribution(_), do: :none
+
+ def run([], opts) do
+ switch_cookie = opts[:erlang_cookie]
+ home_dir = get_home_dir()
+ cookie_file_path = Path.join(home_dir, ".erlang.cookie")
+ cookie_file_stat = case File.stat(Path.join(home_dir, ".erlang.cookie")) do
+ {:error, :enoent} -> nil
+ {:ok, value} -> value
+ end
+ cookie_file_type = case cookie_file_stat do
+ nil -> nil
+ value -> value.type
+ end
+ cookie_file_access = case cookie_file_stat do
+ nil -> nil
+ value -> value.access
+ end
+ cookie_file_size = case cookie_file_stat do
+ nil -> nil
+ value -> value.size
+ end
+
+ %{
+ os_env_cookie_set: System.get_env("RABBITMQ_ERLANG_COOKIE") != nil,
+ os_env_cookie_value_length: String.length(System.get_env("RABBITMQ_ERLANG_COOKIE") || ""),
+ switch_cookie_set: switch_cookie != nil,
+ switch_cookie_value_length: String.length(to_string(switch_cookie) || ""),
+ effective_user: System.get_env("USER"),
+ home_dir: home_dir,
+ cookie_file_path: cookie_file_path,
+ cookie_file_exists: File.exists?(cookie_file_path),
+ cookie_file_type: cookie_file_type,
+ cookie_file_access: cookie_file_access,
+ cookie_file_size: cookie_file_size
+ }
+ end
+
+ def banner([], %{}), do: "Listing Erlang cookie sources used by CLI tools..."
+
+ def output(result, %{formatter: "json"}) do
+ {:ok, result}
+ end
+
+ def output(result, _opts) do
+ cookie_file_lines = [
+ "#{bright("Cookie File")}\n",
+ "Effective user: #{result[:effective_user] || "(none)"}",
+ "Effective home directory: #{result[:home_dir] || "(none)"}",
+ "Cookie file path: #{result[:cookie_file_path]}",
+ "Cookie file exists? #{result[:cookie_file_exists]}",
+ "Cookie file type: #{result[:cookie_file_type] || "(n/a)"}",
+ "Cookie file access: #{result[:cookie_file_access] || "(n/a)"}",
+ "Cookie file size: #{result[:cookie_file_size] || "(n/a)"}",
+ ]
+
+ switch_lines = [
+ "\n#{bright("Cookie CLI Switch")}\n",
+ "--erlang-cookie value set? #{result[:switch_cookie_set]}",
+ "--erlang-cookie value length: #{result[:switch_cookie_value_length] || 0}"
+ ]
+
+ os_env_lines = [
+ "\n#{bright("Env variable ")} #{bright_red("(Deprecated)")}\n",
+ "RABBITMQ_ERLANG_COOKIE value set? #{result[:os_env_cookie_set]}",
+ "RABBITMQ_ERLANG_COOKIE value length: #{result[:os_env_cookie_value_length] || 0}"
+ ]
+
+ lines = cookie_file_lines ++ switch_lines ++ os_env_lines
+
+ {:ok, lines}
+ end
+
+ def help_section(), do: :configuration
+
+ def description() do
+ "Display Erlang cookie source (e.g. $HOME/.erlang.cookie file) information useful for troubleshooting"
+ end
+
+ def usage, do: "erlang_cookie_sources"
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.StringPerLine
+
+ #
+ # Implementation
+ #
+
+ @doc """
+ Computes HOME directory path the same way Erlang VM/ei does,
+ including taking Windows-specific env variables into account.
+ """
+ def get_home_dir() do
+ homedrive = System.get_env("HOMEDRIVE")
+ homepath = System.get_env("HOMEPATH")
+
+ case {homedrive != nil, homepath != nil} do
+ {true, true} -> "#{homedrive}#{homepath}"
+ _ -> System.get_env("HOME")
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_version_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_version_command.ex
new file mode 100644
index 0000000000..053e0d142e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/erlang_version_command.ex
@@ -0,0 +1,72 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ErlangVersionCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches() do
+ [details: :boolean, offline: :boolean, timeout: :integer]
+ end
+ def aliases(), do: [t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{details: false, offline: false}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{details: details, offline: true}) do
+ case details do
+ true ->
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.otp_system_version())
+
+ false ->
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.platform_and_version())
+ end
+ end
+ def run([], %{node: node_name, timeout: timeout, details: details}) do
+ case details do
+ true ->
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.rpc_call(node_name, :rabbit_misc, :otp_system_version, [], timeout))
+
+ false ->
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.rpc_call(node_name, :rabbit_misc, :platform_and_version, [], timeout))
+ end
+ end
+
+ def output(result, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok", "value" => result}}
+ end
+ def output(result, _opts) when is_bitstring(result) do
+ {:ok, result}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Displays Erlang/OTP version on the target node"
+
+ def usage, do: "erlang_version"
+
+ def usage_additional() do
+ [
+ ["--details", "when set, display additional Erlang/OTP system information"],
+ ["--offline", "when set, displays local Erlang/OTP version (that used by CLI tools)"]
+ ]
+ end
+
+ def banner([], %{offline: true}) do
+ "CLI Erlang/OTP version ..."
+ end
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} for its Erlang/OTP version..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_booting_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_booting_command.ex
new file mode 100644
index 0000000000..56b2253c90
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_booting_command.ex
@@ -0,0 +1,53 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.IsBootingCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit, :is_booting, [node_name], timeout)
+ end
+
+ def output(true, %{node: node_name, formatter: "json"}) do
+ m = %{
+ "result" => true,
+ "message" => "RabbitMQ on node #{node_name} is booting"
+ }
+ {:ok, m}
+ end
+
+ def output(false, %{node: node_name, formatter: "json"}) do
+ m = %{
+ "result" => false,
+ "message" => "RabbitMQ on node #{node_name} is fully booted (check with is_running), stopped or has not started booting yet"
+ }
+ {:ok, m}
+ end
+ def output(true, %{node: node_name}) do
+ {:ok, "RabbitMQ on node #{node_name} is booting"}
+ end
+
+ def output(false, %{node: node_name}) do
+ {:ok,
+ "RabbitMQ on node #{node_name} is fully booted (check with is_running), stopped or has not started booting yet"}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Checks if RabbitMQ is still booting on the target node"
+
+ def usage, do: "is_booting"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} for its boot status ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_running_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_running_command.ex
new file mode 100644
index 0000000000..ecf5ce9368
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/is_running_command.ex
@@ -0,0 +1,45 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.IsRunningCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Note: we use is_booted/1 over is_running/1 to avoid
+ # returning a positive result when the node is still booting
+ :rabbit_misc.rpc_call(node_name, :rabbit, :is_booted, [node_name], timeout)
+ end
+
+ def output(true, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => true, "message" => "RabbitMQ on node #{node_name} is fully booted and running"}}
+ end
+ def output(false, %{node: node_name, formatter: "json"}) do
+ {:ok,
+ %{"result" => false, "message" => "RabbitMQ on node #{node_name} is not running or has not fully booted yet (check with is_booting)"}}
+ end
+ def output(true, %{node: node_name}) do
+ {:ok, "RabbitMQ on node #{node_name} is fully booted and running"}
+ end
+ def output(false, %{node: node_name}) do
+ {:ok,
+ "RabbitMQ on node #{node_name} is not running or has not fully booted yet (check with is_booting)"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Checks if RabbitMQ is fully booted and running on the target node"
+
+ def usage, do: "is_running"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} for its status ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_network_interfaces_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_network_interfaces_command.ex
new file mode 100644
index 0000000000..d41409b8c4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_network_interfaces_command.ex
@@ -0,0 +1,77 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ListNetworkInterfacesCommand do
+ @moduledoc """
+ Displays all network interfaces (NICs) reported by the target node.
+ """
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+ import RabbitMQ.CLI.Core.ANSI
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [timeout: :integer, offline: :boolean]
+ def aliases(), do: [t: :timeout]
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{offline: true}) do
+ :rabbit_net.getifaddrs()
+ end
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_net, :getifaddrs, [], timeout)
+ end
+
+ def output(nic_map, %{node: node_name, formatter: "json"}) when map_size(nic_map) == 0 do
+ {:ok, %{"result" => "ok", "node" => node_name, "interfaces" => %{}}}
+ end
+ def output(nic_map, %{node: node_name}) when map_size(nic_map) == 0 do
+ {:ok, "Node #{node_name} reported no network interfaces"}
+ end
+ def output(nic_map0, %{node: node_name, formatter: "json"}) do
+ nic_map = Enum.map(nic_map0, fn ({k, v}) -> {to_string(k), v} end)
+ {:ok,
+ %{
+ "result" => "ok",
+ "interfaces" => Enum.into(nic_map, %{}),
+ "message" => "Node #{node_name} reported network interfaces"
+ }}
+ end
+ def output(nic_map, _) when is_map(nic_map) do
+ lines = nic_lines(nic_map)
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists network interfaces (NICs) on the target node"
+
+ def usage, do: "list_network_interfaces"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} to report its network interfaces ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp nic_lines(nic_map) do
+ Enum.reduce(nic_map, [],
+ fn({iface, props}, acc) ->
+ iface_lines = Enum.reduce(props, [],
+ fn({prop, val}, inner_acc) ->
+ ["#{prop}: #{val}" | inner_acc]
+ end)
+
+ header = "#{bright("Interface #{iface}")}\n"
+ acc ++ [header | iface_lines] ++ ["\n"]
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_node_auth_attempt_stats_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_node_auth_attempt_stats_command.ex
new file mode 100644
index 0000000000..4793cf6c46
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/list_node_auth_attempt_stats_command.ex
@@ -0,0 +1,75 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ListNodeAuthAttemptStatsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def switches(), do: [by_source: :boolean]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{by_source: false}, opts)}
+ end
+
+ def validate([], _), do: :ok
+ def validate(_, _), do: {:validation_failure, :too_many_args}
+
+ def run([], %{node: node_name, timeout: timeout, by_source: by_source}) do
+ case by_source do
+ :true ->
+ :rabbit_misc.rpc_call(
+ node_name, :rabbit_core_metrics, :get_auth_attempts_by_source, [], timeout)
+ :false ->
+ :rabbit_misc.rpc_call(
+ node_name, :rabbit_core_metrics, :get_auth_attempts, [], timeout)
+ end
+ end
+
+ def output([], %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "attempts" => []}}
+ end
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no authentication attempt stats"}
+ end
+ def output(rows, %{node: node_name, formatter: "json"}) do
+ maps = Enum.map(rows, &Map.new/1)
+ {:ok,
+ %{
+ "result" => "ok",
+ "node" => node_name,
+ "attempts" => maps
+ }}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "list_node_auth_attempts [--by-source]"
+
+ def usage_additional do
+ [
+ ["--by-source", "list authentication attempts by remote address and username"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+ def description(), do: "Lists authentication attempts on the target node"
+
+ def banner([], %{node: node_name}), do: "Listing authentication
+ attempts for node \"#{node_name}\" ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/listeners_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/listeners_command.ex
new file mode 100644
index 0000000000..f54ce3775e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/listeners_command.ex
@@ -0,0 +1,92 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ListenersCommand do
+ @moduledoc """
+ Displays all listeners on a node.
+
+ Returns a code of 0 unless there were connectivity and authentication
+ errors. This command is not meant to be used in health checks.
+ """
+
+ import RabbitMQ.CLI.Core.Listeners,
+ only: [listeners_on: 2, listener_lines: 1, listener_maps: 1, listener_rows: 1]
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ # Example listener list:
+ #
+ # [{listener,rabbit@warp10,clustering,"localhost",
+ # {0,0,0,0,0,0,0,0},
+ # 25672,[]},
+ # {listener,rabbit@warp10,amqp,"localhost",
+ # {0,0,0,0,0,0,0,0},
+ # 5672,
+ # [{backlog,128},
+ # {nodelay,true},
+ # {linger,{true,0}},
+ # {exit_on_close,false}]},
+ # {listener,rabbit@warp10,stomp,"localhost",
+ # {0,0,0,0,0,0,0,0},
+ # 61613,
+ # [{backlog,128},{nodelay,true}]}]
+ case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ xs when is_list(xs) -> listeners_on(xs, node_name)
+ other -> other
+ end
+ end
+
+ def output([], %{formatter: fmt}) when fmt == "csv" or fmt == "erlang" do
+ {:ok, []}
+ end
+
+ def output([], %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "listeners" => []}}
+ end
+
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no enabled listeners."}
+ end
+
+ def output(listeners, %{formatter: "erlang"}) do
+ {:ok, listener_rows(listeners)}
+ end
+
+ def output(listeners, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}}
+ end
+
+ def output(listeners, %{formatter: "csv"}) do
+ {:stream, [listener_rows(listeners)]}
+ end
+
+ def output(listeners, _opts) do
+ lines = listener_lines(listeners)
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(),
+ do: "Lists active connection listeners (bound interface, port, protocol) on the target node"
+
+ def usage, do: "listeners"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} to report its protocol listeners ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_location_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_location_command.ex
new file mode 100644
index 0000000000..36ff562b41
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_location_command.ex
@@ -0,0 +1,56 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.LogLocationCommand do
+ @moduledoc """
+ Displays standard log file location on the target node
+ """
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.LogFiles
+
+ def switches, do: [all: :boolean, timeout: :integer]
+ def aliases, do: [a: :all, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{all: false}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout, all: all}) do
+ case all do
+ true -> LogFiles.get_log_locations(node_name, timeout);
+ false -> LogFiles.get_default_log_location(node_name, timeout)
+ end
+ end
+
+ def output({:ok, location}, %{node: node_name, formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "node_name" => node_name,
+ "paths" => [location]
+ }}
+ end
+ def output(locations, %{node: node_name, formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "node_name" => node_name,
+ "paths" => locations
+ }}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Shows log file location(s) on target node"
+
+ def usage, do: "log_location [--all|-a]"
+
+ def banner([], %{node: node_name}) do
+ "Log file location(s) on node #{node_name} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_command.ex
new file mode 100644
index 0000000000..9717908f60
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_command.ex
@@ -0,0 +1,50 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.LogTailCommand do
+ @moduledoc """
+ Displays standard log file location on the target node
+ """
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.LogFiles
+
+ def switches, do: [number: :integer, timeout: :integer]
+ def aliases, do: ['N': :number, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{number: 50}, opts)}
+ end
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout, number: n}) do
+ case LogFiles.get_default_log_location(node_name, timeout) do
+ {:ok, file} ->
+ :rabbit_misc.rpc_call(node_name,
+ :rabbit_log_tail, :tail_n_lines, [file, n],
+ timeout)
+ error -> error
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Prints the last N lines of the log on the node"
+
+ def usage, do: "log_tail [--number|-N <number>]"
+
+ def usage_additional do
+ [
+ ["<number>", "number of lines to print. Defaults to 50"]
+ ]
+ end
+
+ def banner([], %{node: node_name, number: n}) do
+ "Last #{n} log lines on node #{node_name} ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_stream_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_stream_command.ex
new file mode 100644
index 0000000000..5080fd0d1d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/log_tail_stream_command.ex
@@ -0,0 +1,73 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.LogTailStreamCommand do
+ @moduledoc """
+ Displays standard log file location on the target node
+ """
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.LogFiles
+
+
+ def switches(), do: [duration: :integer, timeout: :integer]
+ def aliases(), do: [d: :duration, t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{duration: :infinity}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def printer(), do: RabbitMQ.CLI.Printers.StdIORaw
+
+ def run([], %{node: node_name, timeout: timeout, duration: duration}) do
+ case LogFiles.get_default_log_location(node_name, timeout) do
+ {:ok, file} ->
+ pid = self()
+ ref = make_ref()
+ subscribed = :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_log_tail, :init_tail_stream,
+ [file, pid, ref, duration],
+ timeout)
+ case subscribed do
+ {:ok, ^ref} ->
+ Stream.unfold(:confinue,
+ fn(:finished) -> nil
+ (:confinue) ->
+ receive do
+ {^ref, data, :finished} -> {data, :finished};
+ {^ref, data, :confinue} -> {data, :confinue}
+ end
+ end)
+ error -> error
+ end
+ error -> error
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Streams logs from a running node for a period of time"
+
+ def usage, do: "log_tail_stream [--duration|-d <seconds>]"
+
+ def usage_additional() do
+ [
+ ["<duration_in_seconds>", "duration in seconds to stream log. Defaults to infinity"]
+ ]
+ end
+
+ def banner([], %{node: node_name, duration: :infinity}) do
+ "Streaming logs from node #{node_name} ..."
+ end
+ def banner([], %{node: node_name, duration: duration}) do
+ "Streaming logs from node #{node_name} for #{duration} seconds ..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/maybe_stuck_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/maybe_stuck_command.ex
new file mode 100644
index 0000000000..c241780f62
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/maybe_stuck_command.ex
@@ -0,0 +1,29 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.MaybeStuckCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_diagnostics, :maybe_stuck, [], timeout)
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Detects Erlang processes (\"lightweight threads\") potentially not making progress on the target node"
+
+ def usage, do: "maybe_stuck"
+
+ def banner(_, %{node: node_name}) do
+ "Asking node #{node_name} to detect potentially stuck Erlang processes..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/memory_breakdown_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/memory_breakdown_command.ex
new file mode 100644
index 0000000000..356358b7d7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/memory_breakdown_command.ex
@@ -0,0 +1,103 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.MemoryBreakdownCommand do
+ alias RabbitMQ.CLI.InformationUnit, as: IU
+ import RabbitMQ.CLI.Core.Memory
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [unit: :string, timeout: :integer]
+ def aliases(), do: [t: :timeout]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{unit: "gb"}, opts)}
+ end
+
+ def validate(args, _) when length(args) > 0 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, %{unit: unit}) do
+ case IU.known_unit?(unit) do
+ true ->
+ :ok
+
+ false ->
+ {:validation_failure, "unit '#{unit}' is not supported. Please use one of: bytes, mb, gb"}
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_vm, :memory, [], timeout)
+ end
+
+ def output(result, %{formatter: "json"} = _opts) do
+ {:ok, compute_relative_values(result)}
+ end
+
+ def output(result, %{formatter: "csv"} = _opts) do
+ flattened =
+ compute_relative_values(result)
+ |> Enum.flat_map(fn {k, %{bytes: b, percentage: p}} ->
+ [{"#{k}.bytes", b}, {"#{k}.percentage", p}]
+ end)
+ |> Enum.sort_by(fn {key, _val} -> key end, &>=/2)
+
+ headers = Enum.map(flattened, fn {k, _v} -> k end)
+ values = Enum.map(flattened, fn {_k, v} -> v end)
+
+ {:stream, [headers, values]}
+ end
+
+ def output(result, _opts) do
+ {:ok, compute_relative_values(result)}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Provides a memory usage breakdown on the target node."
+
+ def usage, do: "memory_breakdown [--unit <unit>]"
+
+ def usage_additional() do
+ [
+ ["--unit <bytes | mb | gb>", "byte multiple (bytes, megabytes, gigabytes) to use"],
+ ["--formatter <json | csv | erlang>", "alternative formatter to use, JSON, CSV or Erlang terms"]
+ ]
+ end
+
+ def banner([], %{node: node_name}) do
+ "Reporting memory breakdown on node #{node_name}..."
+ end
+
+ defmodule Formatter do
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+ alias RabbitMQ.CLI.InformationUnit, as: IU
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, %{unit: unit}) do
+ Enum.reduce(output, "", fn {key, %{bytes: bytes, percentage: percentage}}, acc ->
+ u = String.downcase(unit)
+ acc <> "#{key}: #{IU.convert(bytes, u)} #{u} (#{percentage}%)\n"
+ end)
+ end
+
+ def format_stream(stream, options) do
+ Stream.map(
+ stream,
+ FormatterHelpers.without_errors_1(fn el ->
+ format_output(el, options)
+ end)
+ )
+ end
+ end
+
+ def formatter(), do: Formatter
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/observer_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/observer_command.ex
new file mode 100644
index 0000000000..717e23e6b5
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/observer_command.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ObserverCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def switches(), do: [interval: :integer]
+ def aliases(), do: [i: :interval]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{interval: 5}, opts)}
+ end
+
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, interval: interval}) do
+ case :observer_cli.start(node_name, [{:interval, interval * 1000}]) do
+ # See zhongwencool/observer_cli#54
+ {:badrpc, _} = err -> err
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ :ok -> {:ok, "Disconnected from #{node_name}."}
+ :quit -> {:ok, "Disconnected from #{node_name}."}
+ other -> other
+ end
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Starts a CLI observer interface on the target node"
+
+ def usage, do: "observer [--interval <seconds>]"
+
+ def usage_additional() do
+ [
+ ["--interval <seconds>", "Update interval to use, in seconds"]
+ ]
+ end
+
+ def banner(_, %{node: node_name}) do
+ "Starting a CLI observer interface on node #{node_name}..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/os_env_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/os_env_command.ex
new file mode 100644
index 0000000000..63e8c18beb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/os_env_command.ex
@@ -0,0 +1,67 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.OsEnvCommand do
+ @moduledoc """
+ Lists RabbitMQ-specific environment variables defined on target node
+ """
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_env, :get_used_env_vars, [], timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ xs when is_list(xs) ->
+ # convert keys and values to binaries (Elixir strings)
+ xs
+ |> Enum.map(fn {k, v} -> {:rabbit_data_coercion.to_binary(k), :rabbit_data_coercion.to_binary(v)} end)
+ |> :maps.from_list
+ other -> other
+ end
+ end
+
+ def output([], %{formatter: fmt}) when fmt == "csv" or fmt == "erlang" do
+ {:ok, []}
+ end
+ def output([], %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "variables" => []}}
+ end
+ def output([], %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no relevant environment variables."}
+ end
+ def output(vars, %{node: node_name, formatter: "json"}) do
+ {:ok, %{"result" => "ok", "node" => node_name, "variables" => vars}}
+ end
+ def output(vars, %{formatter: "csv"}) do
+ {:stream, [Enum.map(vars, fn({k, v}) -> [variable: k, value: v] end)]}
+ end
+ def output(vars, _opts) do
+ lines = Enum.map(vars, fn({k, v}) -> "#{k}=#{v}" end) |> Enum.join(line_separator())
+ {:ok, lines}
+ end
+
+ def usage() do
+ "os_env"
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Lists RabbitMQ-specific environment variables set on target node"
+
+ def banner(_, %{node: node_name}) do
+ "Listing RabbitMQ-specific environment variables defined on node #{node_name}..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/reset_node_auth_attempt_metrics_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/reset_node_auth_attempt_metrics_command.ex
new file mode 100644
index 0000000000..e3b08c2ac8
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/reset_node_auth_attempt_metrics_command.ex
@@ -0,0 +1,37 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ResetNodeAuthAttemptMetricsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_core_metrics, :reset_auth_attempt_metrics, [])
+ end
+
+ def usage, do: "reset_node_auth_attempt_metrics"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.access_control(),
+ DocGuide.monitoring()
+ ]
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Resets auth attempt metrics on the target node"
+
+ def banner([], %{node: node_name}) do
+ "Reset auth attempt metrics on node #{node_name} ..."
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolve_hostname_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolve_hostname_command.ex
new file mode 100644
index 0000000000..349dbee513
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolve_hostname_command.ex
@@ -0,0 +1,94 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ResolveHostnameCommand do
+ @moduledoc """
+ Resolves a hostname to one or more addresses of a given IP address family (IPv4 ot IPv6).
+ This command is not meant to compete with `dig` but rather provide a way
+ to perform basic resolution tests that take Erlang's inetrc file into account.
+ """
+
+ import RabbitCommon.Records
+ alias RabbitMQ.CLI.Core.Networking
+ alias RabbitMQ.CLI.Core.ExitCodes
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:diagnostics]
+
+ def switches(), do: [address_family: :string, offline: :boolean]
+ def aliases(), do: [a: :address_family]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{address_family: "IPv4", offline: false}, opts)}
+ end
+
+ def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
+ def validate(args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
+ def validate([_], %{address_family: family}) do
+ case Networking.valid_address_family?(family) do
+ true -> :ok
+ false -> {:validation_failure, {:bad_argument, "unsupported IP address family #{family}. Valid values are: ipv4, ipv6"}}
+ end
+ end
+ def validate([_], _), do: :ok
+
+ def run([hostname], %{address_family: family, offline: true}) do
+ :inet.gethostbyname(to_charlist(hostname), Networking.address_family(family))
+ end
+ def run([hostname], %{node: node_name, address_family: family, offline: false, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :inet, :gethostbyname,
+ [to_charlist(hostname), Networking.address_family(family)], timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ {:ok, result} -> {:ok, result}
+ other -> other
+ end
+ end
+
+ def output({:error, :nxdomain}, %{node: node_name, formatter: "json"}) do
+ m = %{
+ "result" => "error",
+ "node" => node_name,
+ "message" => "Hostname does not resolve (resolution failed with an nxdomain)"
+ }
+ {:error, ExitCodes.exit_dataerr(), m}
+ end
+ def output({:error, :nxdomain}, _opts) do
+ {:error, ExitCodes.exit_dataerr(), "Hostname does not resolve (resolution failed with an nxdomain)"}
+ end
+ def output({:ok, result}, %{node: node_name, address_family: family, formatter: "json"}) do
+ hostname = hostent(result, :h_name)
+ addresses = hostent(result, :h_addr_list)
+ {:ok, %{
+ "result" => "ok",
+ "node" => node_name,
+ "hostname" => to_string(hostname),
+ "address_family" => family,
+ "addresses" => Networking.format_addresses(addresses)
+ }}
+ end
+ def output({:ok, result}, _opts) do
+ addresses = hostent(result, :h_addr_list)
+ {:ok, Enum.join(Networking.format_addresses(addresses), "\n")}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "resolve_hostname <hostname> [--address-family <ipv4 | ipv6>]"
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Resolves a hostname to a set of addresses. Takes Erlang's inetrc file into account."
+
+ def banner([hostname], %{offline: false, node: node_name, address_family: family}) do
+ "Asking node #{node_name} to resolve hostname #{hostname} to #{family} addresses..."
+ end
+ def banner([hostname], %{offline: true, address_family: family}) do
+ "Resolving hostname #{hostname} to #{family} addresses..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolver_info_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolver_info_command.ex
new file mode 100644
index 0000000000..a4f3d8d7d3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/resolver_info_command.ex
@@ -0,0 +1,84 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ResolverInfoCommand do
+ @moduledoc """
+ Displays effective hostname resolver (inetrc) configuration on target node
+ """
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+ import RabbitMQ.CLI.Core.ANSI, only: [bright: 1]
+ alias RabbitMQ.CLI.Core.Networking
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def scopes(), do: [:diagnostics]
+
+ def switches(), do: [offline: :boolean]
+ def aliases(), do: []
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{offline: false}, opts)}
+ end
+
+ def validate(args, _) when length(args) > 0, do: {:validation_failure, :too_many_args}
+ def validate([], _), do: :ok
+
+ def run([], %{offline: true}) do
+ Networking.inetrc_map(:inet.get_rc())
+ end
+ def run([], %{node: node_name, timeout: timeout, offline: false}) do
+ case :rabbit_misc.rpc_call(node_name, :inet, :get_rc, [], timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ xs when is_list(xs) -> Networking.inetrc_map(xs)
+ other -> other
+ end
+ end
+
+ def output(info, %{node: node_name, formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "node" => node_name,
+ "resolver" => info
+ }}
+ end
+ def output(info, _opts) do
+ main_section = [
+ "#{bright("Runtime Hostname Resolver (inetrc) Settings")}\n",
+ "Lookup order: #{info["lookup"]}",
+ "Hosts file: #{info["hosts_file"]}",
+ "Resolver conf file: #{info["resolv_conf"]}",
+ "Cache size: #{info["cache_size"]}"
+ ]
+ hosts_section = [
+ "\n#{bright("inetrc File Host Entries")}\n"
+ ] ++ case info["hosts"] do
+ [] -> ["(none)"]
+ nil -> ["(none)"]
+ hs -> Enum.reduce(hs, [], fn {k, v}, acc -> ["#{k} #{Enum.join(v, ", ")}" | acc] end)
+ end
+
+ lines = main_section ++ hosts_section
+
+ {:ok, Enum.join(lines, line_separator())}
+ end
+
+ def usage() do
+ "resolver_info"
+ end
+
+ def help_section(), do: :configuration
+
+ def description(), do: "Displays effective hostname resolver (inetrc) configuration on target node"
+
+ def banner(_, %{node: node_name, offline: false}) do
+ "Asking node #{node_name} for its effective hostname resolver (inetrc) configuration..."
+ end
+ def banner(_, %{offline: true}) do
+ "Displaying effective hostname resolver (inetrc) configuration used by CLI tools..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/runtime_thread_stats_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/runtime_thread_stats_command.ex
new file mode 100644
index 0000000000..ee5bb56566
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/runtime_thread_stats_command.ex
@@ -0,0 +1,70 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.RuntimeThreadStatsCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches(), do: [sample_interval: :integer]
+ def aliases(), do: [i: :sample_interval]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{sample_interval: 5}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout, sample_interval: interval}) do
+ case :rabbit_misc.rpc_call(
+ node_name,
+ :rabbit_runtime,
+ :msacc_stats,
+ [interval * 1000],
+ timeout
+ ) do
+ {:ok, stats} -> stats
+ other -> other
+ end
+ end
+
+ def output(result, %{formatter: "json"}) when is_list(result) do
+ {:error, "JSON formatter is not supported by this command"}
+ end
+
+ def output(result, %{formatter: "csv"}) when is_list(result) do
+ {:error, "CSV formatter is not supported by this command"}
+ end
+
+ def output(result, _options) when is_list(result) do
+ {:ok, result}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Provides a breakdown of runtime thread activity stats on the target node"
+
+ def usage, do: "runtime_thread_stats [--sample-interval <interval>]"
+
+ def usage_additional() do
+ [
+ ["--sample-interval <seconds>", "sampling interval to use in seconds"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.runtime_tuning()
+ ]
+ end
+
+ def banner([], %{node: node_name, sample_interval: interval}) do
+ "Will collect runtime thread stats on #{node_name} for #{interval} seconds..."
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Msacc
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/schema_info_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/schema_info_command.ex
new file mode 100644
index 0000000000..50b750c772
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/schema_info_command.ex
@@ -0,0 +1,73 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.SchemaInfoCommand do
+ @moduledoc """
+ Lists all tables on the mnesia schema
+ """
+
+ alias RabbitMQ.CLI.Ctl.InfoKeys
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @info_keys ~w(name snmp load_order active_replicas all_nodes attributes checkpoints disc_copies
+ disc_only_copies external_copies frag_properties master_nodes ram_copies
+ storage_properties subscribers user_properties cstruct local_content
+ where_to_commit where_to_read name access_mode cookie load_by_force
+ load_node record_name size storage_type type where_to_write index arity
+ majority memory commit_work where_to_wlock load_reason record_validation
+ version wild_pattern index_info)a
+
+ def info_keys(), do: @info_keys
+
+ def scopes(), do: [:ctl, :diagnostics]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def merge_defaults([], opts) do
+ merge_defaults(
+ ~w(name cookie active_replicas user_properties),
+ opts
+ )
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{table_headers: true}, opts)}
+ end
+
+ def validate(args, _) do
+ case InfoKeys.validate_info_keys(args, @info_keys) do
+ {:ok, _} -> :ok
+ err -> err
+ end
+ end
+
+ def run([_ | _] = args, %{node: node_name, timeout: timeout}) do
+ info_keys = InfoKeys.prepare_info_keys(args)
+ :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :schema_info, [info_keys], timeout)
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage() do
+ "schema_info [--no-table-headers] [<column> ...]"
+ end
+
+ def usage_additional() do
+ [
+ ["<column>", "must be one of " <> Enum.join(Enum.sort(@info_keys), ", ")]
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists schema database tables and their properties"
+
+ def banner(_, %{node: node_name}), do: "Asking node #{node_name} to report its schema..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/server_version_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/server_version_command.ex
new file mode 100644
index 0000000000..9f4068e459
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/server_version_command.ex
@@ -0,0 +1,37 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.ServerVersionCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ :rabbit_data_coercion.to_binary(
+ :rabbit_misc.rpc_call(node_name, :rabbit_misc, :version, [], timeout))
+ end
+
+ def output(result, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok", "value" => result}}
+ end
+ def output(result, _options) when is_bitstring(result) do
+ {:ok, result}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Displays server version on the target node"
+
+ def usage, do: "server_version"
+
+ def banner([], %{node: node_name}) do
+ "Asking node #{node_name} for its RabbitMQ version..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/tls_versions_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/tls_versions_command.ex
new file mode 100644
index 0000000000..2f81bad889
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/tls_versions_command.ex
@@ -0,0 +1,39 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Commands.TlsVersionsCommand do
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout} = _opts) do
+ :rabbit_misc.rpc_call(node_name, :ssl, :versions, [], timeout)
+ end
+
+ def banner([], %{}), do: "Listing all TLS versions supported by the runtime..."
+
+ def output(result, %{formatter: "json"}) do
+ vs = Map.new(result) |> Map.get(:available)
+
+ {:ok, %{versions: vs}}
+ end
+
+ def output(result, _opts) do
+ vs = Map.new(result) |> Map.get(:available)
+ {:ok, vs}
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Lists TLS versions supported (but not necessarily allowed) on the target node"
+
+ def usage, do: "tls_versions"
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.StringPerLine
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/diagnostics_helpers.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/diagnostics_helpers.ex
new file mode 100644
index 0000000000..601cc842cb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/diagnostics_helpers.ex
@@ -0,0 +1,38 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Diagnostics.Helpers do
+ def test_connection(hostname, port, timeout) do
+ case :gen_tcp.connect(hostname, port, [], timeout) do
+ {:error, _} -> :gen_tcp.connect(hostname, port, [:inet6], timeout)
+ r -> r
+ end
+ end
+
+ def check_port_connectivity(port, node_name, timeout) do
+ regex = Regex.recompile!(~r/^(.+)@/)
+ hostname = Regex.replace(regex, to_string(node_name), "") |> to_charlist
+ try do
+ case test_connection(hostname, port, timeout) do
+ {:error, _} ->
+ false
+
+ {:ok, port} ->
+ :ok = :gen_tcp.close(port)
+ true
+ end
+
+ # `gen_tcp:connect/4` will throw if the port is outside of its
+ # expected domain
+ catch
+ :exit, _ -> false
+ end
+ end
+
+ def check_listener_connectivity(%{port: port}, node_name, timeout) do
+ check_port_connectivity(port, node_name, timeout)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatter_behaviour.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatter_behaviour.ex
new file mode 100644
index 0000000000..498ba114b9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatter_behaviour.ex
@@ -0,0 +1,42 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Formats returned values e.g. to human-readable text or JSON.
+defmodule RabbitMQ.CLI.FormatterBehaviour do
+ alias RabbitMQ.CLI.Core.Helpers
+
+ @callback format_output(any, map()) :: String.t() | [String.t()]
+ @callback format_stream(Enumerable.t(), map()) :: Enumerable.t()
+
+ @optional_callbacks switches: 0,
+ aliases: 0
+
+ @callback switches() :: Keyword.t()
+ @callback aliases() :: Keyword.t()
+
+ def switches(formatter) do
+ Helpers.apply_if_exported(formatter, :switches, [], [])
+ end
+
+ def aliases(formatter) do
+ Helpers.apply_if_exported(formatter, :aliases, [], [])
+ end
+
+ def module_name(nil) do
+ nil
+ end
+ def module_name(formatter) do
+ mod = formatter |> String.downcase |> Macro.camelize
+ Module.safe_concat("RabbitMQ.CLI.Formatters", mod)
+ end
+
+ def machine_readable?(nil) do
+ false
+ end
+ def machine_readable?(formatter) do
+ Helpers.apply_if_exported(module_name(formatter), :machine_readable?, [], false)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/csv.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/csv.ex
new file mode 100644
index 0000000000..ab9acd613f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/csv.ex
@@ -0,0 +1,127 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.Formatters.Csv do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_stream(stream, _) do
+ ## Flatten list_consumers
+ Stream.flat_map(
+ stream,
+ fn
+ [first | _] = element ->
+ case FormatterHelpers.proplist?(first) or is_map(first) do
+ true -> element
+ false -> [element]
+ end
+
+ other ->
+ [other]
+ end
+ )
+ ## Add info_items names
+ |> Stream.transform(
+ :init,
+ FormatterHelpers.without_errors_2(fn
+ element, :init ->
+ {
+ case keys(element) do
+ nil -> [values(element)]
+ ks -> [ks, values(element)]
+ end,
+ :next
+ }
+
+ element, :next ->
+ {[values(element)], :next}
+ end)
+ )
+ |> CSV.encode(delimiter: "")
+ end
+
+ def format_output(output, _) do
+ case keys(output) do
+ nil -> [values(output)]
+ ks -> [ks, values(output)]
+ end
+ |> CSV.encode()
+ |> Enum.join()
+ end
+
+ def machine_readable?, do: true
+
+ #
+ # Implementation
+ #
+
+ defp keys(map) when is_map(map) do
+ Map.keys(map)
+ end
+
+ defp keys(list) when is_list(list) do
+ case FormatterHelpers.proplist?(list) do
+ true -> Keyword.keys(list)
+ false -> nil
+ end
+ end
+
+ defp keys(_other) do
+ nil
+ end
+
+ defp values(map) when is_map(map) do
+ Map.values(map)
+ end
+
+ defp values([]) do
+ []
+ end
+
+ defp values(list) when is_list(list) do
+ case FormatterHelpers.proplist?(list) do
+ true -> Keyword.values(list)
+ false -> list
+ end
+ end
+
+ defp values(other) do
+ other
+ end
+end
+
+defimpl CSV.Encode, for: PID do
+ def encode(pid, env \\ []) do
+ FormatterHelpers.format_info_item(pid)
+ |> to_string
+ |> CSV.Encode.encode(env)
+ end
+end
+
+defimpl CSV.Encode, for: List do
+ def encode(list, env \\ []) do
+ FormatterHelpers.format_info_item(list)
+ |> to_string
+ |> CSV.Encode.encode(env)
+ end
+end
+
+defimpl CSV.Encode, for: Tuple do
+ def encode(tuple, env \\ []) do
+ FormatterHelpers.format_info_item(tuple)
+ |> to_string
+ |> CSV.Encode.encode(env)
+ end
+end
+
+defimpl CSV.Encode, for: Map do
+ def encode(map, env \\ []) do
+ FormatterHelpers.format_info_item(map)
+ |> to_string
+ |> CSV.Encode.encode(env)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/erlang.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/erlang.ex
new file mode 100644
index 0000000000..0a8a78249f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/erlang.ex
@@ -0,0 +1,18 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.Erlang do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, _) do
+ :io_lib.format("~p", [output])
+ |> to_string
+ end
+
+ def format_stream(stream, options) do
+ [format_output(Enum.to_list(stream), options)]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/formatter_helpers.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/formatter_helpers.ex
new file mode 100644
index 0000000000..2ec4edc3d9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/formatter_helpers.ex
@@ -0,0 +1,182 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.FormatterHelpers do
+ import RabbitCommon.Records
+ use Bitwise
+
+ @type error :: {:error, term()} | {:error, integer(), String.t() | [String.t()]}
+
+ @spec without_errors_1((el -> result)) :: error() | result when el: term(), result: term()
+ def without_errors_1(fun) do
+ fn
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ other -> fun.(other)
+ end
+ end
+
+ @spec without_errors_1((el, acc -> result)) :: error() | result
+ when el: term(), result: term(), acc: term()
+ def without_errors_2(fun) do
+ fn
+ {:error, _} = err, _acc -> err
+ {:error, _, _} = err, _acc -> err
+ other, acc -> fun.(other, acc)
+ end
+ end
+
+ def proplist?([{_key, _value} | rest]), do: proplist?(rest)
+ def proplist?([]), do: true
+ def proplist?(_other), do: false
+
+
+ defmacro is_u8(x) do
+ quote do
+ unquote(x) >= 0 and unquote(x) <= 255
+ end
+ end
+
+ defmacro is_u16(x) do
+ quote do
+ unquote(x) >= 0 and unquote(x) <= 65_535
+ end
+ end
+
+ def format_info_item(item, escaped \\ true)
+
+ def format_info_item(map, escaped) when is_map(map) do
+ [
+ "\#\{",
+ Enum.map(
+ map,
+ fn {k, v} ->
+ ["#{escape(k, escaped)} => ", format_info_item(v, escaped)]
+ end
+ )
+ |> Enum.join(", "),
+ "}"
+ ]
+ end
+
+ # when Record.is_record(res, :resource) do
+ def format_info_item(resource(name: name), escaped) do
+ # resource(name: name) = res
+ escape(name, escaped)
+ end
+
+ def format_info_item({n1, n2, n3, n4} = value, _escaped)
+ when is_u8(n1) and is_u8(n2) and is_u8(n3) and is_u8(n4) do
+ :rabbit_misc.ntoa(value)
+ end
+
+ def format_info_item({k1, k2, k3, k4, k5, k6, k7, k8} = value, _escaped)
+ when is_u16(k1) and is_u16(k2) and is_u16(k3) and is_u16(k4) and
+ is_u16(k5) and is_u16(k6) and is_u16(k7) and is_u16(k8) do
+ :rabbit_misc.ntoa(value)
+ end
+
+ def format_info_item(value, _escaped) when is_pid(value) do
+ :rabbit_misc.pid_to_string(value)
+ end
+
+ def format_info_item(value, escaped) when is_binary(value) do
+ escape(value, escaped)
+ end
+
+ def format_info_item(value, escaped) when is_atom(value) do
+ escape(to_charlist(value), escaped)
+ end
+
+ def format_info_item(
+ [{key, type, _table_entry_value} | _] = value,
+ escaped
+ )
+ when is_binary(key) and
+ is_atom(type) do
+ :io_lib.format(
+ "~1000000000000tp",
+ [prettify_amqp_table(value, escaped)]
+ )
+ end
+
+ def format_info_item([t | _] = value, escaped)
+ when is_tuple(t) or is_pid(t) or is_binary(t) or is_atom(t) or is_list(t) do
+ [
+ "[",
+ Enum.map(
+ value,
+ fn el ->
+ format_info_item(el, escaped)
+ end
+ )
+ |> Enum.join(", "),
+ "]"
+ ]
+ end
+
+ def format_info_item({key, value}, escaped) do
+ ["{", :io_lib.format("~p", [key]), ", ", format_info_item(value, escaped), "}"]
+ end
+
+ def format_info_item(value, _escaped) do
+ :io_lib.format("~1000000000000tp", [value])
+ end
+
+ defp prettify_amqp_table(table, escaped) do
+ for {k, t, v} <- table do
+ {escape(k, escaped), prettify_typed_amqp_value(t, v, escaped)}
+ end
+ end
+
+ defp prettify_typed_amqp_value(:longstr, value, escaped) do
+ escape(value, escaped)
+ end
+
+ defp prettify_typed_amqp_value(:table, value, escaped) do
+ prettify_amqp_table(value, escaped)
+ end
+
+ defp prettify_typed_amqp_value(:array, value, escaped) do
+ for {t, v} <- value, do: prettify_typed_amqp_value(t, v, escaped)
+ end
+
+ defp prettify_typed_amqp_value(_type, value, _escaped) do
+ value
+ end
+
+ defp escape(atom, escaped) when is_atom(atom) do
+ escape(to_charlist(atom), escaped)
+ end
+
+ defp escape(bin, escaped) when is_binary(bin) do
+ escape(to_charlist(bin), escaped)
+ end
+
+ defp escape(l, false) when is_list(l) do
+ escape_char(:lists.reverse(l), [])
+ end
+
+ defp escape(l, true) when is_list(l) do
+ l
+ end
+
+ defp escape_char([?\\ | t], acc) do
+ escape_char(t, [?\\, ?\\ | acc])
+ end
+
+ defp escape_char([x | t], acc) when x >= 32 and x != 127 do
+ escape_char(t, [x | acc])
+ end
+
+ defp escape_char([x | t], acc) do
+ escape_char(t, [?\\, ?0 + (x >>> 6), ?0 + (x &&& 0o070 >>> 3), ?0 + (x &&& 7) | acc])
+ end
+
+ defp escape_char([], acc) do
+ acc
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/inspect.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/inspect.ex
new file mode 100644
index 0000000000..5939007cfe
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/inspect.ex
@@ -0,0 +1,40 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.Formatters.Inspect do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, _) do
+ case is_binary(output) do
+ true -> output
+ false -> inspect(output)
+ end
+ end
+
+ def format_stream(stream, options) do
+ elements =
+ Stream.scan(
+ stream,
+ :empty,
+ FormatterHelpers.without_errors_2(fn element, previous ->
+ separator =
+ case previous do
+ :empty -> ""
+ _ -> ","
+ end
+
+ format_element(element, separator, options)
+ end)
+ )
+
+ Stream.concat([["["], elements, ["]"]])
+ end
+
+ def format_element(val, separator, options) do
+ separator <> format_output(val, options)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json.ex
new file mode 100644
index 0000000000..eb55038715
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json.ex
@@ -0,0 +1,69 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Basic JSON formatter. Supports 1-level of
+# collection using start/finish_collection.
+# Primary purpose is to translate stream from CTL,
+# so there is no need for multiple collection levels
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.Formatters.Json do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, opts) when is_bitstring(output) do
+ format_output(%{"message" => output}, opts)
+ end
+ def format_output(output, _opts) do
+ {:ok, json} = JSON.encode(keys_to_atoms(output))
+ json
+ end
+
+ def format_stream(stream, options) do
+ ## Flatten list_consumers
+ elements =
+ Stream.flat_map(
+ stream,
+ fn
+ [first | _] = element ->
+ case FormatterHelpers.proplist?(first) or is_map(first) do
+ true -> element
+ false -> [element]
+ end
+
+ other ->
+ [other]
+ end
+ )
+ |> Stream.scan(
+ :empty,
+ FormatterHelpers.without_errors_2(fn element, previous ->
+ separator =
+ case previous do
+ :empty -> ""
+ _ -> ","
+ end
+
+ format_element(element, separator, options)
+ end)
+ )
+
+ Stream.concat([["["], elements, ["]"]])
+ end
+
+ def keys_to_atoms(enum) do
+ Enum.map(enum,
+ fn({k, v}) when is_binary(k) or is_list(k) ->
+ {String.to_atom(k), v}
+ (other) -> other
+ end)
+ end
+
+ def format_element(val, separator, options) do
+ separator <> format_output(val, options)
+ end
+
+ def machine_readable?, do: true
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json_stream.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json_stream.ex
new file mode 100644
index 0000000000..a1bea3fc11
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/json_stream.ex
@@ -0,0 +1,75 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Basic JSON formatter. Supports 1-level of
+# collection using start/finish_collection.
+# Primary purpose is to translate stream from CTL,
+# so there is no need for multiple collection levels
+
+defmodule RabbitMQ.CLI.Formatters.JsonStream do
+ @moduledoc """
+ Formats a potentially infinite stream of maps, proplists, keyword lists,
+ and other things that are essentially a map.
+
+ The output exclude JSON array boundaries. The output can be fed
+ to `jq' for pretty printing, filtering and querying.
+ """
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+ alias RabbitMQ.CLI.Core.Platform
+
+ def format_output("", _opts) do
+ # the empty string can be emitted along with a finishing marker that ends the stream
+ # (e.g. with commands that have a duration argument)
+ # we just emit the empty string as the last value for the stream in this case
+ ""
+ end
+ def format_output(output, _opts) do
+ {:ok, json} = JSON.encode(keys_to_atoms(output))
+ json
+ end
+
+ def format_stream(stream, options) do
+ elements =
+ Stream.flat_map(
+ stream,
+ fn
+ [first | _] = element ->
+ case FormatterHelpers.proplist?(first) or is_map(first) do
+ true -> element
+ false -> [element]
+ end
+
+ other ->
+ [other]
+ end
+ )
+ |> Stream.scan(
+ :empty,
+ FormatterHelpers.without_errors_2(fn element, _previous ->
+ format_element(element, options)
+ end)
+ )
+
+ elements
+ end
+
+ def keys_to_atoms(enum) do
+ Enum.map(enum,
+ fn({k, v}) when is_binary(k) or is_list(k) ->
+ {String.to_atom(k), v}
+ (other) -> other
+ end)
+ end
+
+ def format_element(val, options) do
+ format_output(val, options) <> Platform.line_separator()
+ end
+
+ def machine_readable?, do: true
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/msacc.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/msacc.ex
new file mode 100644
index 0000000000..992475a2d0
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/msacc.ex
@@ -0,0 +1,19 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.Msacc do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, _) do
+ {:ok, io} = StringIO.open("")
+ :msacc.print(io, output, %{})
+ StringIO.flush(io)
+ end
+
+ def format_stream(stream, options) do
+ [format_output(Enum.to_list(stream), options)]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/plugins.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/plugins.ex
new file mode 100644
index 0000000000..54881cc32f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/plugins.ex
@@ -0,0 +1,247 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.Formatters.Plugins do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(
+ %{status: status, format: format, plugins: plugins},
+ options
+ ) do
+ legend(status, format, options) ++ format_plugins(plugins, format)
+ end
+
+ def format_output(%{enabled: enabled, mode: _} = output, options) do
+ case length(enabled) do
+ 0 ->
+ ["Plugin configuration unchanged."]
+
+ _ ->
+ [
+ "The following plugins have been enabled:"
+ | for plugin <- enabled do
+ " #{plugin}"
+ end
+ ] ++
+ [""] ++
+ applying(output, options) ++
+ log_offline(output)
+ end
+ end
+
+ def format_output(%{disabled: disabled, mode: _} = output, options) do
+ case length(disabled) do
+ 0 ->
+ ["Plugin configuration unchanged."]
+
+ _ ->
+ [
+ "The following plugins have been disabled:"
+ | for plugin <- disabled do
+ " #{plugin}"
+ end
+ ] ++
+ [""] ++
+ applying(output, options) ++
+ log_offline(output)
+ end
+ end
+
+ ## Do not print enabled/disabled for set command
+ def format_output(%{} = output, options) do
+ applying(output, options)
+ end
+
+ def format_output([], %{node: node}) do
+ ["All plugins have been disabled.", "Applying plugin configuration to #{node}..."]
+ end
+
+ def format_output(plugins, %{node: node}) when is_list(plugins) do
+ [
+ "The following plugins have been configured:"
+ | for plugin <- plugins do
+ " #{plugin}"
+ end
+ ] ++
+ ["Applying plugin configuration to #{node}..."]
+ end
+
+ def format_output(output, _) do
+ :io_lib.format("~p", [output])
+ |> to_string
+ end
+
+ def format_stream(stream, options) do
+ Stream.map(
+ stream,
+ FormatterHelpers.without_errors_1(fn element ->
+ format_output(element, options)
+ end)
+ )
+ end
+
+ defp format_plugins(plugins, format) do
+ max_name_length =
+ Enum.reduce(plugins, 0, fn %{name: name}, len ->
+ max(String.length(to_string(name)), len)
+ end)
+
+ for plugin <- plugins do
+ format_plugin(plugin, format, max_name_length)
+ end
+ |> List.flatten()
+ end
+
+ defp format_plugin(%{name: name}, :minimal, _) do
+ to_string(name)
+ end
+
+ defp format_plugin(plugin, :normal, max_name_length) do
+ [summary(plugin) <> inline_version(plugin, max_name_length)]
+ end
+
+ defp format_plugin(plugin, :verbose, _) do
+ [summary(plugin) | verbose(plugin)]
+ end
+
+ defp summary(%{name: name, enabled: enabled, running: running}) do
+ enabled_sign =
+ case enabled do
+ :implicit -> "e"
+ :enabled -> "E"
+ :not_enabled -> " "
+ end
+
+ running_sign =
+ case running do
+ true -> "*"
+ false -> " "
+ end
+
+ "[#{enabled_sign}#{running_sign}] #{name}"
+ end
+
+ defp inline_version(%{name: name} = plugin, max_name_length) do
+ spacing =
+ String.duplicate(
+ " ",
+ max_name_length -
+ String.length(to_string(name))
+ )
+
+ spacing <> " " <> augment_version(plugin)
+ end
+
+ defp verbose(%{dependencies: dependencies, description: description} = plugin) do
+ prettified = to_string(:io_lib.format("~p", [dependencies]))
+
+ [
+ " Version: \t#{augment_version(plugin)}",
+ " Dependencies:\t#{prettified}",
+ " Description: \t#{description}"
+ ]
+ end
+
+ defp augment_version(%{version: version, running_version: nil}) do
+ to_string(version)
+ end
+
+ defp augment_version(%{version: version, running_version: version}) do
+ to_string(version)
+ end
+
+ defp augment_version(%{version: version, running_version: running_version}) do
+ "#{running_version} (pending upgrade to #{version})"
+ end
+
+ ## Do not print legend in minimal, quiet or silent mode
+ defp legend(_, :minimal, _) do
+ []
+ end
+ defp legend(_, _, %{quiet: true}) do
+ []
+ end
+ defp legend(_, _, %{silent: true}) do
+ []
+ end
+
+ defp legend(status, _, %{node: node}) do
+ [
+ " Configured: E = explicitly enabled; e = implicitly enabled",
+ " | Status: #{status_message(status, node)}",
+ " |/"
+ ]
+ end
+
+ defp status_message(:running, node) do
+ "* = running on #{node}"
+ end
+
+ defp status_message(:node_down, node) do
+ "[failed to contact #{node} - status not shown]"
+ end
+
+ defp applying(%{mode: :offline, set: set_plugins}, _) do
+ set_plugins_message =
+ case length(set_plugins) do
+ 0 -> "nothing to do"
+ len -> "set #{len} plugins"
+ end
+
+ [set_plugins_message <> "."]
+ end
+
+ defp applying(%{mode: :offline, enabled: enabled}, _) do
+ enabled_message =
+ case length(enabled) do
+ 0 -> "nothing to do"
+ len -> "enabled #{len} plugins"
+ end
+
+ [enabled_message <> "."]
+ end
+
+ defp applying(%{mode: :offline, disabled: disabled}, _) do
+ disabled_message =
+ case length(disabled) do
+ 0 -> "nothing to do"
+ len -> "disabled #{len} plugins"
+ end
+
+ [disabled_message <> "."]
+ end
+
+ defp applying(%{mode: :online, started: started, stopped: stopped}, _) do
+ stopped_message =
+ case length(stopped) do
+ 0 -> []
+ len -> ["stopped #{len} plugins"]
+ end
+
+ started_message =
+ case length(started) do
+ 0 -> []
+ len -> ["started #{len} plugins"]
+ end
+
+ change_message =
+ case Enum.join(started_message ++ stopped_message, " and ") do
+ "" -> "nothing to do"
+ msg -> msg
+ end
+
+ [change_message <> "."]
+ end
+
+ defp log_offline(%{mode: :offline}) do
+ ["Offline change; changes will take effect at broker restart."]
+ end
+
+ defp log_offline(_) do
+ []
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/pretty_table.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/pretty_table.ex
new file mode 100644
index 0000000000..6b9b7ed9fd
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/pretty_table.ex
@@ -0,0 +1,87 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.PrettyTable do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+ require Record
+ import Record
+
+ defrecord :table , extract(:table,
+ from_lib: "stdout_formatter/include/stdout_formatter.hrl")
+ defrecord :cell, extract(:cell,
+ from_lib: "stdout_formatter/include/stdout_formatter.hrl")
+ defrecord :paragraph, extract(:paragraph,
+ from_lib: "stdout_formatter/include/stdout_formatter.hrl")
+
+ def format_stream(stream, _opts) do
+ # Flatten for list_consumers
+ entries_with_keys = Stream.flat_map(stream,
+ fn([first | _] = element) ->
+ case FormatterHelpers.proplist?(first) or is_map(first) do
+ true -> element;
+ false -> [element]
+ end
+ (other) ->
+ [other]
+ end)
+ |> Enum.to_list()
+
+ # Use stdout_formatter library to format the table.
+ case entries_with_keys do
+ [first_entry | _] ->
+ col_headers = Stream.map(first_entry,
+ fn({key, _}) ->
+ cell(content: key, props: %{:title => true})
+ end)
+ |> Enum.to_list()
+ rows = Stream.map(entries_with_keys,
+ fn(element) ->
+ Stream.map(element,
+ fn({_, value}) ->
+ cell(content: value, props: %{})
+ end)
+ |> Enum.to_list()
+ end)
+ |> Enum.to_list()
+ ret = :stdout_formatter.to_string(
+ table(
+ rows: [col_headers | rows],
+ props: %{:cell_padding => {0, 1}}))
+ [to_string ret]
+ [] ->
+ entries_with_keys
+ end
+ end
+
+ def format_output(output, _opts) do
+ format = case is_binary(output) do
+ true -> "~s"
+ false -> "~p"
+ end
+ ret = :stdout_formatter.to_string(
+ table(
+ rows: [
+ [cell(content: "Output", props: %{:title => true})],
+ [cell(
+ content: paragraph(content: output,
+ props: %{:format => format}))]],
+ props: %{:cell_padding => {0, 1}}))
+ to_string ret
+ end
+
+ def format_value(value) do
+ case is_binary(value) do
+ true -> value
+ false -> case is_atom(value) do
+ true -> to_string(value)
+ false -> inspect(value)
+ end
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/report.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/report.ex
new file mode 100644
index 0000000000..4db89d611f
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/report.ex
@@ -0,0 +1,52 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2017-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.Report do
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+ alias RabbitMQ.CLI.Core.{Output, Config}
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+ def format_output(_, _) do
+ raise "format_output is not implemented for report formatter"
+ end
+
+ def format_stream(stream, options) do
+ quiet = options[:quiet] || options[:silent] || false
+
+ Stream.flat_map(
+ stream,
+ FormatterHelpers.without_errors_1(fn
+ {_command, _banner, {:error, _} = err} ->
+ err
+
+ {_command, _banner, {:error, _, _} = err} ->
+ err
+
+ {command, banner, result} ->
+ case quiet do
+ true ->
+ Stream.concat([""], format_result(command, result, options))
+
+ false ->
+ Stream.concat(["" | banner_list(banner)], format_result(command, result, options))
+ end
+ end)
+ )
+ end
+
+ def format_result(command, output, options) do
+ formatter = Config.get_formatter(command, options)
+
+ case Output.format_output(output, formatter, options) do
+ :ok -> []
+ {:ok, val} -> [val]
+ {:stream, stream} -> stream
+ end
+ end
+
+ def banner_list([_ | _] = list), do: list
+ def banner_list(val), do: [val]
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string.ex
new file mode 100644
index 0000000000..6fd7f2e0e3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string.ex
@@ -0,0 +1,26 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+## Prints values from a command as strings(if possible)
+defmodule RabbitMQ.CLI.Formatters.String do
+ alias RabbitMQ.CLI.Core.Helpers
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, _) do
+ Helpers.string_or_inspect(output)
+ end
+
+ def format_stream(stream, options) do
+ Stream.map(
+ stream,
+ FormatterHelpers.without_errors_1(fn el ->
+ format_output(el, options)
+ end)
+ )
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string_per_line.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string_per_line.ex
new file mode 100644
index 0000000000..4761b9a555
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/string_per_line.ex
@@ -0,0 +1,42 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Formatters.StringPerLine do
+ @doc """
+ Use this to output one stream (collection) element per line,
+ using the string formatter. Flattens the stream.
+ """
+
+ alias RabbitMQ.CLI.Formatters.FormatterHelpers
+ alias RabbitMQ.CLI.Core.Helpers
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def format_output(output, _) do
+ Enum.map(output, fn el -> Helpers.string_or_inspect(el) end)
+ end
+
+ def format_stream(stream, options) do
+ Stream.scan(
+ stream,
+ :empty,
+ FormatterHelpers.without_errors_2(fn element, previous ->
+ separator =
+ case previous do
+ :empty -> ""
+ _ -> line_separator()
+ end
+
+ format_element(element, separator, options)
+ end)
+ )
+ end
+
+ def format_element(val, separator, options) do
+ separator <> format_output(val, options)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/table.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/table.ex
new file mode 100644
index 0000000000..72d1682202
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/table.ex
@@ -0,0 +1,138 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+alias RabbitMQ.CLI.Formatters.FormatterHelpers
+
+defmodule RabbitMQ.CLI.Formatters.Table do
+ @behaviour RabbitMQ.CLI.FormatterBehaviour
+
+ def switches(), do: [table_headers: :boolean, pad_to_header: :boolean]
+
+ def format_stream(stream, options) do
+ # Flatten for list_consumers
+ Stream.flat_map(
+ stream,
+ fn
+ [first | _] = element ->
+ case FormatterHelpers.proplist?(first) or is_map(first) do
+ true -> element
+ false -> [element]
+ end
+
+ other ->
+ [other]
+ end
+ )
+ |> Stream.transform(
+ :init,
+ FormatterHelpers.without_errors_2(fn
+ element, :init ->
+ {maybe_header(element, options), :next}
+
+ element, :next ->
+ {[format_output_1(element, options)], :next}
+ end)
+ )
+ end
+
+ def format_output(output, options) do
+ maybe_header(output, options)
+ end
+
+ defp maybe_header(output, options) do
+ opt_table_headers = Map.get(options, :table_headers, true)
+ opt_silent = Map.get(options, :silent, false)
+
+ case {opt_silent, opt_table_headers} do
+ {true, _} ->
+ [format_output_1(output, options)]
+
+ {false, false} ->
+ [format_output_1(output, options)]
+
+ {false, true} ->
+ format_header(output) ++ [format_output_1(output, options)]
+ end
+ end
+
+ defp format_output_1(output, options) when is_map(output) do
+ escaped = escaped?(options)
+ pad_to_header = pad_to_header?(options)
+ format_line(output, escaped, pad_to_header)
+ end
+
+ defp format_output_1([], _) do
+ ""
+ end
+
+ defp format_output_1(output, options) do
+ escaped = escaped?(options)
+ pad_to_header = pad_to_header?(options)
+
+ case FormatterHelpers.proplist?(output) do
+ true -> format_line(output, escaped, pad_to_header)
+ false -> format_inspect(output)
+ end
+ end
+
+ defp escaped?(_), do: true
+
+ defp pad_to_header?(%{pad_to_header: pad}), do: pad
+ defp pad_to_header?(_), do: false
+
+ defp format_line(line, escaped, pad_to_header) do
+ values =
+ Enum.map(
+ line,
+ fn {k, v} ->
+ line = FormatterHelpers.format_info_item(v, escaped)
+
+ case pad_to_header do
+ true ->
+ String.pad_trailing(
+ to_string(line),
+ String.length(to_string(k))
+ )
+
+ false ->
+ line
+ end
+ end
+ )
+
+ Enum.join(values, "\t")
+ end
+
+ defp format_inspect(output) do
+ case is_binary(output) do
+ true -> output
+ false -> inspect(output)
+ end
+ end
+
+ @spec format_header(term()) :: [String.t()]
+ defp format_header(output) do
+ keys =
+ case output do
+ map when is_map(map) ->
+ Map.keys(map)
+
+ keyword when is_list(keyword) ->
+ case FormatterHelpers.proplist?(keyword) do
+ true -> Keyword.keys(keyword)
+ false -> []
+ end
+
+ _ ->
+ []
+ end
+
+ case keys do
+ [] -> []
+ _ -> [Enum.join(keys, "\t")]
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/information_unit.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/information_unit.ex
new file mode 100644
index 0000000000..ebef8de0ba
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/information_unit.ex
@@ -0,0 +1,68 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.InformationUnit do
+ require MapSet
+
+ @kilobyte_bytes 1000
+ @megabyte_bytes @kilobyte_bytes * 1000
+ @gigabyte_bytes @megabyte_bytes * 1000
+ @terabyte_bytes @gigabyte_bytes * 1000
+
+ def known_units() do
+ MapSet.new([
+ "bytes",
+ "kb",
+ "kilobytes",
+ "mb",
+ "megabytes",
+ "gb",
+ "gigabytes",
+ "tb",
+ "terabytes"
+ ])
+ end
+
+ def parse(val) do
+ :rabbit_resource_monitor_misc.parse_information_unit(val)
+ end
+
+ def convert(bytes, "bytes") do
+ bytes
+ end
+
+ def convert(bytes, unit) do
+ do_convert(bytes, String.downcase(unit))
+ end
+
+ def known_unit?(val) do
+ MapSet.member?(known_units(), String.downcase(val))
+ end
+
+ defp do_convert(bytes, "kb") do
+ Float.round(bytes / @kilobyte_bytes, 4)
+ end
+
+ defp do_convert(bytes, "kilobytes"), do: do_convert(bytes, "kb")
+
+ defp do_convert(bytes, "mb") do
+ Float.round(bytes / @megabyte_bytes, 4)
+ end
+
+ defp do_convert(bytes, "megabytes"), do: do_convert(bytes, "mb")
+
+ defp do_convert(bytes, "gb") do
+ Float.round(bytes / @gigabyte_bytes, 4)
+ end
+
+ defp do_convert(bytes, "gigabytes"), do: do_convert(bytes, "gb")
+
+ defp do_convert(bytes, "tb") do
+ Float.round(bytes / @terabyte_bytes, 4)
+ end
+
+ defp do_convert(bytes, "terabytes"), do: do_convert(bytes, "tb")
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/directories_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/directories_command.ex
new file mode 100644
index 0000000000..c3b6aecc0d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/directories_command.ex
@@ -0,0 +1,134 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.DirectoriesCommand do
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators, Config}
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, %{offline: true} = opts) do
+ {args, opts}
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{online: true, offline: false}, opts)}
+ end
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{offline: false}), do: :cli
+
+ def switches(), do: [online: :boolean, offline: :boolean]
+
+ def validate(_, %{online: true, offline: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
+ end
+
+ def validate(_, %{online: false, offline: false}) do
+ {:validation_failure, {:bad_argument, "Cannot set online and offline to false"}}
+ end
+
+ def validate([_ | _], _) do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([], _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, %{offline: true} = opts) do
+ Validators.chain(
+ [
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def validate_execution_environment(args, %{online: true} = opts) do
+ Validators.node_is_running(args, opts)
+ end
+
+ def run([], %{online: true, node: node_name}) do
+ do_run(fn key ->
+ :rabbit_misc.rpc_call(node_name, :rabbit_plugins, key, [])
+ end)
+ end
+
+ def run([], %{offline: true} = opts) do
+ do_run(fn key ->
+ Config.get_option(key, opts)
+ end)
+ end
+
+ def output({:ok, _map} = res, %{formatter: "json"}) do
+ res
+ end
+
+ def output({:ok, map}, _opts) do
+ s = """
+ Plugin archives directory: #{Map.get(map, :plugins_dir)}
+ Plugin expansion directory: #{Map.get(map, :plugins_expand_dir)}
+ Enabled plugins file: #{Map.get(map, :enabled_plugins_file)}
+ """
+
+ {:ok, String.trim_trailing(s)}
+ end
+
+ def output({:error, err}, _opts) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_software(), err}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([], %{offline: true}) do
+ "Listing plugin directories inferred from local environment..."
+ end
+
+ def banner([], %{online: true, node: node}) do
+ "Listing plugin directories used by node #{node}"
+ end
+
+ def usage, do: "directories [--offline] [--online]"
+
+ def usage_additional() do
+ [
+ ["--offline", "do not contact target node. Try to infer directories from the environment."],
+ ["--online", "infer directories from target node."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Displays plugin directory and enabled plugin file paths"
+
+ #
+ # Implementation
+ #
+
+ defp do_run(fun) do
+ # return an error or an {:ok, map} tuple
+ Enum.reduce([:plugins_dir, :plugins_expand_dir, :enabled_plugins_file], {:ok, %{}}, fn
+ _, {:error, err} ->
+ {:error, err}
+
+ key, {:ok, acc} ->
+ case fun.(key) do
+ {:error, err} -> {:error, err}
+ val -> {:ok, Map.put(acc, key, :rabbit_data_coercion.to_binary(val))}
+ end
+ end)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/disable_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/disable_command.ex
new file mode 100644
index 0000000000..4fea2ad34e
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/disable_command.ex
@@ -0,0 +1,146 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.DisableCommand do
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Plugins
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{online: false, offline: false, all: false}, opts)}
+ end
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{offline: false}), do: :cli
+
+ def switches(), do: [online: :boolean, offline: :boolean, all: :boolean]
+
+ def validate([], %{all: false}) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _], %{all: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both --all and a list of plugins"}}
+ end
+
+ def validate(_, %{online: true, offline: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
+ end
+
+ def validate(_args, _opts) do
+ :ok
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &PluginHelpers.can_set_plugins_with_mode/2,
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run(plugin_names, %{all: all_flag, node: node_name} = opts) do
+ plugins =
+ case all_flag do
+ false -> for s <- plugin_names, do: String.to_atom(s)
+ true -> PluginHelpers.plugin_names(PluginHelpers.list(opts))
+ end
+
+ enabled = PluginHelpers.read_enabled(opts)
+ all = PluginHelpers.list(opts)
+ implicit = :rabbit_plugins.dependencies(false, enabled, all)
+ to_disable_deps = :rabbit_plugins.dependencies(true, plugins, all)
+ plugins_to_set = MapSet.difference(MapSet.new(enabled), MapSet.new(to_disable_deps))
+
+ mode = PluginHelpers.mode(opts)
+
+ case PluginHelpers.set_enabled_plugins(MapSet.to_list(plugins_to_set), opts) do
+ {:ok, enabled_plugins} ->
+ {:stream,
+ Stream.concat([
+ [:rabbit_plugins.strictly_plugins(enabled_plugins, all)],
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ :timer.sleep(5000)
+
+ case PluginHelpers.update_enabled_plugins(enabled_plugins, mode, node_name, opts) do
+ %{set: new_enabled} = result ->
+ disabled = implicit -- new_enabled
+
+ filter_strictly_plugins(
+ Map.put(result, :disabled, :rabbit_plugins.strictly_plugins(disabled, all)),
+ all,
+ [:set, :started, :stopped]
+ )
+
+ other ->
+ other
+ end
+ end)
+ ])}
+
+ {:error, _} = err ->
+ err
+ end
+ end
+
+ use RabbitMQ.CLI.Plugins.ErrorOutput
+
+ def banner([], %{all: true, node: node_name}) do
+ "Disabling ALL plugins on node #{node_name}"
+ end
+
+ def banner(plugins, %{node: node_name}) do
+ ["Disabling plugins on node #{node_name}:" | plugins]
+ end
+
+ def usage, do: "disable <plugin1> [ <plugin2>] | --all [--offline] [--online]"
+
+ def usage_additional() do
+ [
+ ["<plugin1> [ <plugin2>]", "names of plugins to disable separated by a space"],
+ ["--online", "contact target node to disable the plugins. Changes are applied immediately."],
+ ["--offline", "update enabled plugins file directly without contacting target node. Changes will be delayed until the node is restarted."],
+ ["--all", "disable all currently enabled plugins"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def help_section(), do: :plugin_management
+
+ def description(), do: "Disables one or more plugins"
+
+ #
+ # Implementation
+ #
+
+ defp filter_strictly_plugins(map, _all, []) do
+ map
+ end
+
+ defp filter_strictly_plugins(map, all, [head | tail]) do
+ case map[head] do
+ nil ->
+ filter_strictly_plugins(map, all, tail)
+
+ other ->
+ value = :rabbit_plugins.strictly_plugins(other, all)
+ filter_strictly_plugins(Map.put(map, head, value), all, tail)
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/enable_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/enable_command.ex
new file mode 100644
index 0000000000..530a2cbb6a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/enable_command.ex
@@ -0,0 +1,156 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.EnableCommand do
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Plugins
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{online: false, offline: false, all: false}, opts)}
+ end
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{offline: false}), do: :cli
+
+ def switches(), do: [online: :boolean, offline: :boolean, all: :boolean]
+
+ def validate([], %{all: false}) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _], %{all: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both --all and a list of plugins"}}
+ end
+
+ def validate(_, %{online: true, offline: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
+ end
+
+ def validate(_, _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &PluginHelpers.can_set_plugins_with_mode/2,
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run(plugin_names, %{all: all_flag} = opts) do
+ plugins =
+ case all_flag do
+ false -> for s <- plugin_names, do: String.to_atom(s)
+ true -> PluginHelpers.plugin_names(PluginHelpers.list(opts))
+ end
+
+ case PluginHelpers.validate_plugins(plugins, opts) do
+ :ok -> do_run(plugins, opts)
+ other -> other
+ end
+ end
+
+ use RabbitMQ.CLI.Plugins.ErrorOutput
+
+ def banner([], %{all: true, node: node_name}) do
+ "Enabling ALL plugins on node #{node_name}"
+ end
+
+ def banner(plugins, %{node: node_name}) do
+ ["Enabling plugins on node #{node_name}:" | plugins]
+ end
+
+ def usage, do: "enable <plugin1> [ <plugin2>] | --all [--offline] [--online]"
+
+ def usage_additional() do
+ [
+ ["<plugin1> [ <plugin2>]", "names of plugins to enable separated by a space"],
+ ["--online", "contact target node to enable the plugins. Changes are applied immediately."],
+ ["--offline", "update enabled plugins file directly without contacting target node. Changes will be delayed until the node is restarted."],
+ ["--all", "enable all available plugins. Not recommended as some plugins may conflict or otherwise be incompatible!"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def help_section(), do: :plugin_management
+
+ def description(), do: "Enables one or more plugins"
+
+ #
+ # Implementation
+ #
+
+ def do_run(plugins, %{node: node_name} = opts) do
+ enabled = PluginHelpers.read_enabled(opts)
+ all = PluginHelpers.list(opts)
+ implicit = :rabbit_plugins.dependencies(false, enabled, all)
+ enabled_implicitly = MapSet.difference(MapSet.new(implicit), MapSet.new(enabled))
+
+ plugins_to_set =
+ MapSet.union(
+ MapSet.new(enabled),
+ MapSet.difference(MapSet.new(plugins), enabled_implicitly)
+ )
+
+ mode = PluginHelpers.mode(opts)
+
+ case PluginHelpers.set_enabled_plugins(MapSet.to_list(plugins_to_set), opts) do
+ {:ok, enabled_plugins} ->
+ {:stream,
+ Stream.concat([
+ [:rabbit_plugins.strictly_plugins(enabled_plugins, all)],
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ case PluginHelpers.update_enabled_plugins(enabled_plugins, mode, node_name, opts) do
+ %{set: new_enabled} = result ->
+ enabled = new_enabled -- implicit
+
+ filter_strictly_plugins(
+ Map.put(result, :enabled, :rabbit_plugins.strictly_plugins(enabled, all)),
+ all,
+ [:set, :started, :stopped]
+ )
+
+ other ->
+ other
+ end
+ end)
+ ])}
+
+ {:error, _} = err ->
+ err
+ end
+ end
+
+ defp filter_strictly_plugins(map, _all, []) do
+ map
+ end
+
+ defp filter_strictly_plugins(map, all, [head | tail]) do
+ case map[head] do
+ nil ->
+ filter_strictly_plugins(map, all, tail)
+
+ other ->
+ value = :rabbit_plugins.strictly_plugins(other, all)
+ filter_strictly_plugins(Map.put(map, head, value), all, tail)
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/is_enabled.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/is_enabled.ex
new file mode 100644
index 0000000000..fa54b1eee3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/is_enabled.ex
@@ -0,0 +1,154 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.IsEnabledCommand do
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, %{offline: true} = opts) do
+ {args, opts}
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{online: true, offline: false}, opts)}
+ end
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{offline: false}), do: :cli
+
+ def switches(), do: [online: :boolean, offline: :boolean]
+
+ def validate(_, %{online: true, offline: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
+ end
+
+ def validate(_, %{online: false, offline: false}) do
+ {:validation_failure, {:bad_argument, "Cannot set online and offline to false"}}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate([_ | _], _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, %{offline: true} = opts) do
+ Validators.chain(
+ [
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def validate_execution_environment(args, %{online: true} = opts) do
+ Validators.node_is_running(args, opts)
+ end
+
+ def run(args, %{online: true, node: node_name} = opts) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_plugins, :active, []) do
+ {:error, _} = e ->
+ e
+
+ plugins ->
+ plugins = Enum.map(plugins, &Atom.to_string/1) |> Enum.sort()
+
+ case Enum.filter(args, fn x -> not Enum.member?(plugins, x) end) do
+ [] -> {:ok, positive_result_message(args, opts)}
+ xs -> {:error, negative_result_message(xs, opts, plugins)}
+ end
+ end
+ end
+
+ def run(args, %{offline: true} = opts) do
+ plugins = PluginHelpers.list_names(opts) |> Enum.map(&Atom.to_string/1) |> Enum.sort()
+
+ case Enum.filter(args, fn x -> not Enum.member?(plugins, x) end) do
+ [] -> {:ok, positive_result_message(args, opts)}
+ xs -> {:error, negative_result_message(xs, opts, plugins)}
+ end
+ end
+
+ def output({:ok, msg}, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok", "message" => msg}}
+ end
+
+ def output({:error, msg}, %{formatter: "json"}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_unavailable(),
+ %{"result" => "error", "message" => msg}}
+ end
+
+ def output({:error, err}, _opts) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_unavailable(), err}
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "is_enabled <plugin1> [ <plugin2>] [--offline] [--online]"
+
+ def usage_additional() do
+ [
+ ["<plugin1> [ <plugin2>]", "names of plugins to check separated by a space"],
+ ["--online", "contact target node to perform the check. Requires the node to be running and reachable."],
+ ["--offline", "check enabled plugins file directly without contacting target node."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def banner(args, %{offline: true}) do
+ "Inferring if #{plugin_or_plugins(args)} from local environment..."
+ end
+
+ def banner(args, %{online: true, node: node}) do
+ "Asking node #{node} if #{plugin_or_plugins(args)} enabled..."
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Health check that exits with a non-zero code if provided plugins are not enabled on target node"
+
+ #
+ # Implementation
+ #
+
+ def plugin_or_plugins(args) when length(args) == 1 do
+ "plugin #{PluginHelpers.comma_separated_names(args)} is"
+ end
+
+ def plugin_or_plugins(args) when length(args) > 1 do
+ "plugins #{PluginHelpers.comma_separated_names(args)} are"
+ end
+
+ defp positive_result_message(args, %{online: true, node: node_name}) do
+ String.capitalize("#{plugin_or_plugins(args)} enabled on node #{node_name}")
+ end
+
+ defp positive_result_message(args, %{offline: true}) do
+ String.capitalize("#{plugin_or_plugins(args)} enabled")
+ end
+
+ defp negative_result_message(missing, %{online: true, node: node_name}, plugins) do
+ String.capitalize("#{plugin_or_plugins(missing)} not enabled on node #{node_name}. ") <>
+ "Enabled plugins and dependencies: #{PluginHelpers.comma_separated_names(plugins)}"
+ end
+
+ defp negative_result_message(missing, %{offline: true}, plugins) do
+ String.capitalize("#{plugin_or_plugins(missing)} not enabled. ") <>
+ "Enabled plugins and dependencies: #{PluginHelpers.comma_separated_names(plugins)}"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/list_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/list_command.ex
new file mode 100644
index 0000000000..a4e943a149
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/list_command.ex
@@ -0,0 +1,188 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.ListCommand do
+ import RabbitCommon.Records
+
+ alias RabbitMQ.CLI.Core.{DocGuide, Validators}
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Plugins
+
+ def merge_defaults([], opts), do: merge_defaults([".*"], opts)
+ def merge_defaults(args, opts), do: {args, Map.merge(default_opts(), opts)}
+
+ def switches(),
+ do: [verbose: :boolean, minimal: :boolean, enabled: :boolean, implicitly_enabled: :boolean]
+
+ def aliases(), do: [v: :verbose, m: :minimal, E: :enabled, e: :implicitly_enabled]
+
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate(_, %{verbose: true, minimal: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both verbose and minimal"}}
+ end
+
+ def validate(_, _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run([pattern], %{node: node_name} = opts) do
+ %{verbose: verbose, minimal: minimal, enabled: only_enabled, implicitly_enabled: all_enabled} =
+ opts
+
+ all = PluginHelpers.list(opts)
+ enabled = PluginHelpers.read_enabled(opts)
+
+ missing = MapSet.difference(MapSet.new(enabled), MapSet.new(PluginHelpers.plugin_names(all)))
+
+ case Enum.empty?(missing) do
+ true ->
+ :ok
+
+ false ->
+ names = Enum.join(Enum.to_list(missing), ", ")
+ IO.puts("WARNING - plugins currently enabled but missing: #{names}\n")
+ end
+
+ implicit = :rabbit_plugins.dependencies(false, enabled, all)
+ enabled_implicitly = implicit -- enabled
+
+ {status, running} =
+ case remote_running_plugins(node_name) do
+ :error -> {:node_down, []}
+ {:ok, active} -> {:running, active}
+ end
+
+ {:ok, re} = Regex.compile(pattern)
+
+ format =
+ case {verbose, minimal} do
+ {true, false} -> :verbose
+ {false, true} -> :minimal
+ {false, false} -> :normal
+ end
+
+ plugins =
+ Enum.filter(
+ all,
+ fn plugin ->
+ name = PluginHelpers.plugin_name(plugin)
+
+ :rabbit_plugins.is_strictly_plugin(plugin) and
+ Regex.match?(re, to_string(name)) and
+ cond do
+ only_enabled -> Enum.member?(enabled, name)
+ all_enabled -> Enum.member?(enabled ++ enabled_implicitly, name)
+ true -> true
+ end
+ end
+ )
+
+ %{
+ status: status,
+ format: format,
+ plugins: format_plugins(plugins, format, enabled, enabled_implicitly, running)
+ }
+ end
+
+ def banner([pattern], _), do: "Listing plugins with pattern \"#{pattern}\" ..."
+
+ def usage, do: "list [pattern] [--verbose] [--minimal] [--enabled] [--implicitly-enabled]"
+
+ def usage_additional() do
+ [
+ ["<pattern>", "only list plugins that match a regular expression pattern"],
+ ["--verbose", "output more information"],
+ ["--minimal", "only print plugin names. Most useful in compbination with --silent and --enabled."],
+ ["--enabled", "only list enabled plugins"],
+ ["--implicitly-enabled", "include plugins enabled as dependencies of other plugins"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def help_section(), do: :plugin_management
+
+ def description(), do: "Lists plugins and their state"
+
+ #
+ # Implementation
+ #
+
+ defp remote_running_plugins(node) do
+ case :rabbit_misc.rpc_call(node, :rabbit_plugins, :running_plugins, []) do
+ {:badrpc, _} -> :error
+ active_with_version -> active_with_version
+ end
+ end
+
+ defp format_plugins(plugins, format, enabled, enabled_implicitly, running) do
+ plugins
+ |> sort_plugins
+ |> Enum.map(fn plugin ->
+ format_plugin(plugin, format, enabled, enabled_implicitly, running)
+ end)
+ end
+
+ defp sort_plugins(plugins) do
+ Enum.sort_by(plugins, &PluginHelpers.plugin_name/1)
+ end
+
+ defp format_plugin(plugin, :minimal, _, _, _) do
+ %{name: PluginHelpers.plugin_name(plugin)}
+ end
+
+ defp format_plugin(plugin, :normal, enabled, enabled_implicitly, running) do
+ plugin(name: name, version: version) = plugin
+
+ enabled_mode =
+ case {Enum.member?(enabled, name), Enum.member?(enabled_implicitly, name)} do
+ {true, false} -> :enabled
+ {false, true} -> :implicit
+ {false, false} -> :not_enabled
+ end
+
+ %{
+ name: name,
+ version: version,
+ running_version: running[name],
+ enabled: enabled_mode,
+ running: Keyword.has_key?(running, name)
+ }
+ end
+
+ defp format_plugin(plugin, :verbose, enabled, enabled_implicitly, running) do
+ normal = format_plugin(plugin, :normal, enabled, enabled_implicitly, running)
+ plugin(dependencies: dependencies, description: description) = plugin
+ Map.merge(normal, %{dependencies: dependencies, description: description})
+ end
+
+ defp default_opts() do
+ %{minimal: false, verbose: false, enabled: false, implicitly_enabled: false}
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/set_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/set_command.ex
new file mode 100644
index 0000000000..68b442a547
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/commands/set_command.ex
@@ -0,0 +1,133 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Commands.SetCommand do
+ alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
+ alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Validators}
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Plugins
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{online: false, offline: false}, opts)}
+ end
+
+ def distribution(%{offline: true}), do: :none
+ def distribution(%{offline: false}), do: :cli
+
+ def switches(), do: [online: :boolean, offline: :boolean]
+
+ def validate(_, %{online: true, offline: true}) do
+ {:validation_failure, {:bad_argument, "Cannot set both online and offline"}}
+ end
+
+ def validate(_, _) do
+ :ok
+ end
+
+ def validate_execution_environment(args, opts) do
+ Validators.chain(
+ [
+ &PluginHelpers.can_set_plugins_with_mode/2,
+ &require_rabbit_and_plugins/2,
+ &PluginHelpers.enabled_plugins_file/2,
+ &plugins_dir/2
+ ],
+ [args, opts]
+ )
+ end
+
+ def run(plugin_names, opts) do
+ plugins = for s <- plugin_names, do: String.to_atom(s)
+
+ case PluginHelpers.validate_plugins(plugins, opts) do
+ :ok -> do_run(plugins, opts)
+ other -> other
+ end
+ end
+
+ def output({:error, {:plugins_not_found, missing}}, _opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "The following plugins were not found: #{Enum.join(Enum.to_list(missing), ", ")}"}
+ end
+
+ use RabbitMQ.CLI.Plugins.ErrorOutput
+
+ def banner(plugins, %{node: node_name}) do
+ ["Enabling plugins on node #{node_name}:" | plugins]
+ end
+
+ def usage, do: "set <plugin1> [ <plugin2>] [--offline] [--online]"
+
+ def usage_additional() do
+ [
+ ["<plugin1> [ <plugin2>]", "names of plugins to enable separated by a space. All other plugins will be disabled."],
+ ["--online", "contact target node to enable the plugins. Changes are applied immediately."],
+ ["--offline", "update enabled plugins file directly without contacting target node. Changes will be delayed until the node is restarted."]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.plugins()
+ ]
+ end
+
+ def help_section(), do: :plugin_management
+
+ def description(), do: "Enables one or more plugins, disables the rest"
+
+ #
+ # Implementation
+ #
+
+ def do_run(plugins, %{node: node_name} = opts) do
+ all = PluginHelpers.list(opts)
+ mode = PluginHelpers.mode(opts)
+
+ case PluginHelpers.set_enabled_plugins(plugins, opts) do
+ {:ok, enabled_plugins} ->
+ {:stream,
+ Stream.concat([
+ [:rabbit_plugins.strictly_plugins(enabled_plugins, all)],
+ RabbitMQ.CLI.Core.Helpers.defer(fn ->
+ case PluginHelpers.update_enabled_plugins(
+ enabled_plugins,
+ mode,
+ node_name,
+ opts
+ ) do
+ %{set: _} = map ->
+ filter_strictly_plugins(map, all, [:set, :started, :stopped])
+
+ {:error, _} = err ->
+ err
+ end
+ end)
+ ])}
+
+ {:error, _} = err ->
+ err
+ end
+ end
+
+ defp filter_strictly_plugins(map, _all, []) do
+ map
+ end
+
+ defp filter_strictly_plugins(map, all, [head | tail]) do
+ case map[head] do
+ nil ->
+ filter_strictly_plugins(map, all, tail)
+
+ other ->
+ value = :rabbit_plugins.strictly_plugins(other, all)
+ filter_strictly_plugins(Map.put(map, head, value), all, tail)
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/error_output.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/error_output.ex
new file mode 100644
index 0000000000..51c75ed99a
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/error_output.ex
@@ -0,0 +1,55 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+# Default output implementation for plugin commands
+defmodule RabbitMQ.CLI.Plugins.ErrorOutput do
+ alias RabbitMQ.CLI.Core.ExitCodes
+
+ defmacro __using__(_) do
+ quote do
+ def output({:error, {:enabled_plugins_mismatch, cli_path, node_path}}, opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{cli_path}: target node #{opts[:node]} uses a different path (#{
+ node_path
+ })"}
+ end
+
+ def output({:error, {:cannot_read_enabled_plugins_file, path, :eacces}}, _opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not read enabled plugins file at #{path}: the file does not exist or permission was denied (EACCES)"}
+ end
+
+ def output({:error, {:cannot_read_enabled_plugins_file, path, :enoent}}, _opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not read enabled plugins file at #{path}: the file does not exist (ENOENT)"}
+ end
+
+ def output({:error, {:cannot_write_enabled_plugins_file, path, :eacces}}, _opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{path}: the file does not exist or permission was denied (EACCES)"}
+ end
+
+ def output({:error, {:cannot_write_enabled_plugins_file, path, :enoent}}, _opts) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{path}: the file does not exist (ENOENT)"}
+ end
+
+ def output({:error, err}, _opts) do
+ {:error, ExitCodes.exit_software(), err}
+ end
+
+ def output({:stream, stream}, _opts) do
+ {:stream, stream}
+ end
+ end
+
+ # quote
+ end
+
+ # defmacro
+end
+
+# defmodule
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/plugins_helpers.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/plugins_helpers.ex
new file mode 100644
index 0000000000..bf8b4f772b
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/plugins/plugins_helpers.ex
@@ -0,0 +1,232 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Plugins.Helpers do
+ import RabbitMQ.CLI.Core.DataCoercion
+ import RabbitCommon.Records
+ import RabbitMQ.CLI.Core.Platform, only: [path_separator: 0]
+ import RabbitMQ.CLI.Core.{CodePath, Paths}
+ alias RabbitMQ.CLI.Core.{Config, Validators}
+
+ def mode(opts) do
+ %{online: online, offline: offline} = opts
+
+ case {online, offline} do
+ {true, false} -> :online
+ {false, true} -> :offline
+ {false, false} -> :best_effort
+ end
+ end
+
+ def can_set_plugins_with_mode(args, opts) do
+ case mode(opts) do
+ # can always set offline plugins list
+ :offline ->
+ :ok
+
+ # assume online mode, fall back to offline mode in case of errors
+ :best_effort ->
+ :ok
+
+ # a running node is required
+ :online ->
+ Validators.chain(
+ [&Validators.node_is_running/2, &Validators.rabbit_is_running/2],
+ [args, opts]
+ )
+ end
+ end
+
+ def list(opts) do
+ {:ok, dir} = plugins_dir(opts)
+ add_all_to_path(dir)
+ :lists.usort(:rabbit_plugins.list(to_charlist(dir)))
+ end
+
+ def list_names(opts) do
+ list(opts) |> plugin_names
+ end
+
+ def read_enabled(opts) do
+ case enabled_plugins_file(opts) do
+ {:ok, enabled} ->
+ :rabbit_plugins.read_enabled(to_charlist(enabled))
+
+ # Existence of enabled_plugins_file should be validated separately
+ {:error, :no_plugins_file} ->
+ []
+ end
+ end
+
+ def enabled_plugins_file(opts) do
+ case Config.get_option(:enabled_plugins_file, opts) do
+ nil -> {:error, :no_plugins_file}
+ file -> {:ok, file}
+ end
+ end
+
+ def enabled_plugins_file(_, opts) do
+ enabled_plugins_file(opts)
+ end
+
+ def set_enabled_plugins(plugins, opts) do
+ plugin_atoms = :lists.usort(for plugin <- plugins, do: to_atom(plugin))
+ require_rabbit_and_plugins(opts)
+ {:ok, plugins_file} = enabled_plugins_file(opts)
+ write_enabled_plugins(plugin_atoms, plugins_file, opts)
+ end
+
+ @spec update_enabled_plugins(
+ [atom()],
+ :online | :offline | :best_effort,
+ node(),
+ map()
+ ) :: map() | {:error, any()}
+ def update_enabled_plugins(enabled_plugins, mode, node_name, opts) do
+ {:ok, plugins_file} = enabled_plugins_file(opts)
+
+ case mode do
+ :online ->
+ case update_enabled_plugins(node_name, plugins_file) do
+ {:ok, started, stopped} ->
+ %{
+ mode: :online,
+ started: Enum.sort(started),
+ stopped: Enum.sort(stopped),
+ set: Enum.sort(enabled_plugins)
+ }
+
+ {:error, _} = err ->
+ err
+ end
+
+ :best_effort ->
+ case update_enabled_plugins(node_name, plugins_file) do
+ {:ok, started, stopped} ->
+ %{
+ mode: :online,
+ started: Enum.sort(started),
+ stopped: Enum.sort(stopped),
+ set: Enum.sort(enabled_plugins)
+ }
+
+ {:error, :offline} ->
+ %{mode: :offline, set: Enum.sort(enabled_plugins)}
+
+ {:error, {:enabled_plugins_mismatch, _, _}} = err ->
+ err
+ end
+
+ :offline ->
+ %{mode: :offline, set: Enum.sort(enabled_plugins)}
+ end
+ end
+
+ def validate_plugins(requested_plugins, opts) do
+ ## Maybe check all plugins
+ plugins =
+ case opts do
+ %{all: true} -> plugin_names(list(opts))
+ _ -> requested_plugins
+ end
+
+ all = list(opts)
+ deps = :rabbit_plugins.dependencies(false, plugins, all)
+
+ deps_plugins =
+ Enum.filter(all, fn plugin ->
+ name = plugin_name(plugin)
+ Enum.member?(deps, name)
+ end)
+
+ case :rabbit_plugins.validate_plugins(deps_plugins) do
+ {_, []} -> :ok
+ {_, invalid} -> {:error, :rabbit_plugins.format_invalid_plugins(invalid)}
+ end
+ end
+
+ def plugin_name(plugin) when is_binary(plugin) do
+ plugin
+ end
+
+ def plugin_name(plugin) when is_atom(plugin) do
+ Atom.to_string(plugin)
+ end
+
+ def plugin_name(plugin) do
+ plugin(name: name) = plugin
+ name
+ end
+
+ def plugin_names(plugins) do
+ for plugin <- plugins, do: plugin_name(plugin)
+ end
+
+ def comma_separated_names(plugins) do
+ Enum.join(plugin_names(plugins), ", ")
+ end
+
+ #
+ # Implementation
+ #
+
+ defp to_list(str) when is_binary(str) do
+ :erlang.binary_to_list(str)
+ end
+
+ defp to_list(lst) when is_list(lst) do
+ lst
+ end
+
+ defp to_list(atm) when is_atom(atm) do
+ to_list(Atom.to_string(atm))
+ end
+
+ defp write_enabled_plugins(plugins, plugins_file, opts) do
+ all = list(opts)
+ all_plugin_names = Enum.map(all, &plugin_name/1)
+ missing = MapSet.difference(MapSet.new(plugins), MapSet.new(all_plugin_names))
+
+ case Enum.empty?(missing) do
+ true ->
+ case :rabbit_file.write_term_file(to_charlist(plugins_file), [plugins]) do
+ :ok ->
+ all_enabled = :rabbit_plugins.dependencies(false, plugins, all)
+ {:ok, Enum.sort(all_enabled)}
+
+ {:error, reason} ->
+ {:error, {:cannot_write_enabled_plugins_file, plugins_file, reason}}
+ end
+
+ false ->
+ {:error, {:plugins_not_found, Enum.to_list(missing)}}
+ end
+ end
+
+ defp update_enabled_plugins(node_name, plugins_file) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_plugins, :ensure, [to_list(plugins_file)]) do
+ {:badrpc, :nodedown} -> {:error, :offline}
+ {:error, :rabbit_not_running} -> {:error, :offline}
+ {:ok, start, stop} -> {:ok, start, stop}
+ {:error, _} = err -> err
+ end
+ end
+
+ defp add_all_to_path(plugins_directories) do
+ directories = String.split(to_string(plugins_directories), path_separator())
+
+ Enum.map(directories, fn directory ->
+ with {:ok, subdirs} <- File.ls(directory) do
+ for subdir <- subdirs do
+ Path.join([directory, subdir, "ebin"])
+ |> Code.append_path()
+ end
+ end
+ end)
+
+ :ok
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/printer_behaviour.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/printer_behaviour.ex
new file mode 100644
index 0000000000..b2bedfdaad
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/printer_behaviour.ex
@@ -0,0 +1,21 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.PrinterBehaviour do
+ @callback init(options :: map()) :: {:ok, printer_state :: any} | {:error, error :: any}
+ @callback finish(printer_state :: any) :: :ok
+
+ @callback print_output(output :: String.t() | [String.t()], printer_state :: any) :: :ok
+ @callback print_ok(printer_state :: any) :: :ok
+
+ def module_name(nil) do
+ nil
+ end
+ def module_name(printer) do
+ mod = printer |> String.downcase |> Macro.camelize
+ String.to_atom("RabbitMQ.CLI.Printers." <> mod)
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/file.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/file.ex
new file mode 100644
index 0000000000..ba0daaeebb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/file.ex
@@ -0,0 +1,36 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Printers.File do
+ @behaviour RabbitMQ.CLI.PrinterBehaviour
+
+ def init(options) do
+ file = options[:file]
+
+ case File.open(file) do
+ {:ok, io_device} -> {:ok, %{device: io_device}}
+ {:error, err} -> {:error, err}
+ end
+ end
+
+ def finish(%{device: io_device}) do
+ :ok = File.close(io_device)
+ end
+
+ def print_output(output, %{device: io_device}) when is_list(output) do
+ for line <- output do
+ IO.puts(io_device, line)
+ end
+ end
+
+ def print_output(output, %{device: io_device}) do
+ IO.puts(io_device, output)
+ end
+
+ def print_ok(_) do
+ :ok
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io.ex
new file mode 100644
index 0000000000..206feff56d
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io.ex
@@ -0,0 +1,28 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Printers.StdIO do
+ @behaviour RabbitMQ.CLI.PrinterBehaviour
+
+ def init(_), do: {:ok, :ok}
+ def finish(_), do: :ok
+
+ def print_output(nil, _), do: :ok
+
+ def print_output(output, _) when is_list(output) do
+ for line <- output do
+ IO.puts(line)
+ end
+ end
+
+ def print_output(output, _) do
+ IO.puts(output)
+ end
+
+ def print_ok(_) do
+ :ok
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io_raw.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io_raw.ex
new file mode 100644
index 0000000000..16846907b4
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/printers/std_io_raw.ex
@@ -0,0 +1,28 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Printers.StdIORaw do
+ @behaviour RabbitMQ.CLI.PrinterBehaviour
+
+ def init(_), do: {:ok, :ok}
+ def finish(_), do: :ok
+
+ def print_output(nil, _), do: :ok
+
+ def print_output(output, _) when is_list(output) do
+ for line <- output do
+ IO.write(line)
+ end
+ end
+
+ def print_output(output, _) do
+ IO.write(output)
+ end
+
+ def print_ok(_) do
+ :ok
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/add_member_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/add_member_command.ex
new file mode 100644
index 0000000000..e789d00343
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/add_member_command.ex
@@ -0,0 +1,70 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.AddMemberCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 5_000
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ other -> other
+ end
+ {args, Map.merge(%{vhost: "/", timeout: timeout}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, node] = _args, %{vhost: vhost, node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :add_member, [
+ vhost,
+ name,
+ to_atom(node),
+ timeout
+ ]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot add members to a classic queue"}
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "add_member [--vhost <vhost>] <queue> <node>"
+
+ def usage_additional do
+ [
+ ["<queue>", "quorum queue name"],
+ ["<node>", "node to add a new replica on"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section, do: :replication
+
+ def description, do: "Adds a quorum queue member (replica) on the given node."
+
+ def banner([name, node], _) do
+ [
+ "Adding a replica for queue #{name} on node #{node}..."
+ ]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_mirror_sync_critical_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_mirror_sync_critical_command.ex
new file mode 100644
index 0000000000..c31b83d29c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_mirror_sync_critical_command.ex
@@ -0,0 +1,105 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsMirrorSyncCriticalCommand do
+ @moduledoc """
+ Exits with a non-zero code if there are classic mirrored queues that don't
+ have any in sync mirrors online and would potentially lose data
+ if the target node is shut down.
+
+ This command is meant to be used as a pre-upgrade (pre-shutdown) check.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ def scopes(), do: [:diagnostics, :queues]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name,
+ :rabbit_nodes, :is_single_node_cluster, [], timeout) do
+ # if target node is the only one in the cluster, the check makes little sense
+ # and false positives can be misleading
+ true -> {:ok, :single_node_cluster}
+ false ->
+ case :rabbit_misc.rpc_call(node_name,
+ :rabbit_amqqueue, :list_local_mirrored_classic_without_synchronised_mirrors_for_cli, [], timeout) do
+ [] -> {:ok, []}
+ qs when is_list(qs) -> {:ok, qs}
+ other -> other
+ end
+ other -> other
+ end
+ end
+
+ def output({:ok, :single_node_cluster}, %{formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "message" => "Target node seems to be the only one in a single node cluster, the check does not apply"
+ }}
+ end
+ def output({:ok, []}, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+ def output({:ok, :single_node_cluster}, %{silent: true}) do
+ {:ok, :check_passed}
+ end
+ def output({:ok, []}, %{silent: true}) do
+ {:ok, :check_passed}
+ end
+ def output({:ok, :single_node_cluster}, %{node: node_name}) do
+ {:ok, "Node #{node_name} seems to be the only one in a single node cluster, the check does not apply"}
+ end
+ def output({:ok, []}, %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no classic mirrored queues without online synchronised mirrors"}
+ end
+ def output({:ok, qs}, %{node: node_name, formatter: "json"}) when is_list(qs) do
+ {:error, :check_failed,
+ %{
+ "result" => "error",
+ "queues" => qs,
+ "message" => "Node #{node_name} reported local classic mirrored queues without online synchronised mirrors"
+ }}
+ end
+ def output({:ok, qs}, %{silent: true}) when is_list(qs) do
+ {:error, :check_failed}
+ end
+ def output({:ok, qs}, %{node: node_name}) when is_list(qs) do
+ lines = queue_lines(qs, node_name)
+
+ {:error, :check_failed, Enum.join(lines, line_separator())}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description() do
+ "Health check that exits with a non-zero code if there are classic mirrored queues " <>
+ "without online synchronised mirrors (queues that would potentially lose data if the target node is shut down)"
+ end
+
+ def usage, do: "check_if_node_is_mirror_sync_critical"
+
+ def banner([], %{node: node_name}) do
+ "Checking if node #{node_name} is critical for data safety of any classic mirrored queues ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ def queue_lines(qs, node_name) do
+ for q <- qs do
+ "#{q["readable_name"]} would lose its only synchronised replica (master) if node #{node_name} is stopped"
+ end
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_quorum_critical_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_quorum_critical_command.ex
new file mode 100644
index 0000000000..d8f4a34c1c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/check_if_node_is_quorum_critical_command.ex
@@ -0,0 +1,118 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.CheckIfNodeIsQuorumCriticalCommand do
+ @moduledoc """
+ Exits with a non-zero code if there are quorum queues that would lose their quorum
+ if the target node is shut down.
+
+ This command is meant to be used as a pre-upgrade (pre-shutdown) check.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
+
+ def scopes(), do: [:diagnostics, :queues]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_nodes, :is_single_node_cluster, [], timeout) do
+ # if target node is the only one in the cluster, the check makes little sense
+ # and false positives can be misleading
+ true -> {:ok, :single_node_cluster}
+ false ->
+ case :rabbit_misc.rpc_call(node_name, :rabbit_maintenance, :is_being_drained_local_read, [node_name]) do
+ # if target node is under maintenance, it has already transferred all of its quorum queue
+ # replicas. Don't consider it to be quorum critical. See rabbitmq/rabbitmq-server#2469
+ true -> {:ok, :under_maintenance}
+ false ->
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :list_with_minimum_quorum_for_cli, [], timeout) do
+ [] -> {:ok, []}
+ qs when is_list(qs) -> {:ok, qs}
+ other -> other
+ end
+ end
+ other -> other
+ end
+ end
+
+ def output({:ok, :single_node_cluster}, %{formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "message" => "Target node seems to be the only one in a single node cluster, the check does not apply"
+ }}
+ end
+ def output({:ok, :under_maintenance}, %{formatter: "json"}) do
+ {:ok, %{
+ "result" => "ok",
+ "message" => "Target node seems to be in maintenance mode, the check does not apply"
+ }}
+ end
+ def output({:ok, []}, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok"}}
+ end
+ def output({:ok, :single_node_cluster}, %{silent: true}) do
+ {:ok, :check_passed}
+ end
+ def output({:ok, :under_maintenance}, %{silent: true}) do
+ {:ok, :check_passed}
+ end
+ def output({:ok, []}, %{silent: true}) do
+ {:ok, :check_passed}
+ end
+ def output({:ok, :single_node_cluster}, %{node: node_name}) do
+ {:ok, "Node #{node_name} seems to be the only one in a single node cluster, the check does not apply"}
+ end
+ def output({:ok, :under_maintenance}, %{node: node_name}) do
+ {:ok, "Node #{node_name} seems to be in maintenance mode, the check does not apply"}
+ end
+ def output({:ok, []}, %{node: node_name}) do
+ {:ok, "Node #{node_name} reported no quorum queues with minimum quorum"}
+ end
+ def output({:ok, qs}, %{node: node_name, formatter: "json"}) when is_list(qs) do
+ {:error, :check_failed,
+ %{
+ "result" => "error",
+ "queues" => qs,
+ "message" => "Node #{node_name} reported local queues with minimum online quorum"
+ }}
+ end
+ def output({:ok, qs}, %{silent: true}) when is_list(qs) do
+ {:error, :check_failed}
+ end
+ def output({:ok, qs}, %{node: node_name}) when is_list(qs) do
+ lines = queue_lines(qs, node_name)
+
+ {:error, :check_failed, Enum.join(lines, line_separator())}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description() do
+ "Health check that exits with a non-zero code if there are queues " <>
+ "with minimum online quorum (queues that would lose their quorum if the target node is shut down)"
+ end
+
+ def usage, do: "check_if_node_is_quorum_critical"
+
+ def banner([], %{node: node_name}) do
+ "Checking if node #{node_name} is critical for quorum of any quorum queues ..."
+ end
+
+ #
+ # Implementation
+ #
+
+ def queue_lines(qs, node_name) do
+ for q <- qs, do: "#{q["readable_name"]} would lose quorum if node #{node_name} is stopped"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/delete_member_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/delete_member_command.ex
new file mode 100644
index 0000000000..1579bf6809
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/delete_member_command.ex
@@ -0,0 +1,58 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.DeleteMemberCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, node] = _args, %{vhost: vhost, node: node_name}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :delete_member, [
+ vhost,
+ name,
+ to_atom(node)
+ ]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot add members to a classic queue"}
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "delete_member [--vhost <vhost>] <queue> <node>"
+
+ def usage_additional do
+ [
+ ["<queue>", "quorum queue name"],
+ ["<node>", "node to remove a new replica on"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section, do: :replication
+
+ def description, do: "Removes a quorum queue member (replica) on the given node."
+
+ def banner([name, node], _) do
+ "Removing a replica of queue #{name} on node #{node}..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/grow_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/grow_command.ex
new file mode 100644
index 0000000000..4e0ce903fe
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/grow_command.ex
@@ -0,0 +1,126 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.GrowCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ defp default_opts, do: %{vhost_pattern: ".*",
+ queue_pattern: ".*",
+ errors_only: false}
+
+ def switches(),
+ do: [
+ vhost_pattern: :string,
+ queue_pattern: :string,
+ errors_only: :boolean
+ ]
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(default_opts(), opts)}
+ end
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate([_, s], _) do
+ case s do
+ "all" -> :ok
+ "even" -> :ok
+ _ ->
+ {:validation_failure, "strategy '#{s}' is not recognised."}
+ end
+ end
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([node, strategy], %{node: node_name,
+ vhost_pattern: vhost_pat,
+ queue_pattern: queue_pat,
+ errors_only: errors_only}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :grow, [
+ to_atom(node),
+ vhost_pat,
+ queue_pat,
+ to_atom(strategy)]) do
+ {:error, _} = error -> error;
+ {:badrpc, _} = error -> error;
+ results when errors_only ->
+ for {{:resource, vhost, _kind, name}, {:errors, _, _} = res} <- results,
+ do: [{:vhost, vhost},
+ {:name, name},
+ {:size, format_size res},
+ {:result, format_result res}]
+ results ->
+ for {{:resource, vhost, _kind, name}, res} <- results,
+ do: [{:vhost, vhost},
+ {:name, name},
+ {:size, format_size res},
+ {:result, format_result res}]
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def usage, do: "grow <node> <all | even> [--vhost-pattern <pattern>] [--queue-pattern <pattern>]"
+
+ def usage_additional do
+ [
+ ["<node>", "node name to place replicas on"],
+ ["<all | even>", "how many matching quorum queues should have a replica added on this node: all or half (evenly numbered)?"],
+ ["--queue-pattern <pattern>", "regular expression to match queue names"],
+ ["--vhost-pattern <pattern>", "regular expression to match virtual host names"],
+ ["--errors-only", "only list queues which reported an error"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section, do: :cluster_management
+
+ def description, do: "Grows quorum queue clusters by adding a member (replica) to all or half of matching quorum queues on the given node."
+
+ def banner([node, strategy], _) do
+ "Growing #{strategy} quorum queues on #{node}..."
+ end
+
+ #
+ # Implementation
+ #
+
+ defp format_size({:ok, size}) do
+ size
+ end
+ defp format_size({:error, _size, :timeout}) do
+ # the actual size is uncertain here
+ "?"
+ end
+ defp format_size({:error, size, _}) do
+ size
+ end
+
+ defp format_result({:ok, _size}) do
+ "ok"
+ end
+ defp format_result({:error, _size, :timeout}) do
+ "error: the operation timed out and may not have been completed"
+ end
+ defp format_result({:error, _size, err}) do
+ :io.format "error: ~W", [err, 10]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/peek_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/peek_command.ex
new file mode 100644
index 0000000000..a159c119e9
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/peek_command.ex
@@ -0,0 +1,112 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.PeekCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ def scopes(), do: [:queues]
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+
+ use RabbitMQ.CLI.Core.MergesDefaultVirtualHost
+
+ def validate(args, _) when length(args) < 2 do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 2 do
+ {:validation_failure, :too_many_args}
+ end
+ def validate([_, raw_pos], _) do
+ pos = case Integer.parse(raw_pos) do
+ {n, _} -> n
+ :error -> :error
+ _ -> :error
+ end
+
+ invalid_pos = {:validation_failure, "position value must be a positive integer"}
+ case pos do
+ :error -> invalid_pos
+ num when num < 1 -> invalid_pos
+ num when num >= 1 -> :ok
+ end
+ end
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, pos] = _args, %{node: node_name, vhost: vhost}) do
+ {pos, _} = Integer.parse(pos)
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :peek, [vhost, name, pos]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot peek into a classic queue"}
+ {:ok, msg} ->
+ {:ok, msg}
+ err ->
+ err
+ end
+ end
+
+ def output({:error, :not_found}, %{vhost: vhost, formatter: "json"}) do
+ {:error,
+ %{
+ "result" => "error",
+ "message" => "Target queue was not found in virtual host '#{vhost}'"
+ }}
+ end
+ def output({:error, :no_message_at_pos}, %{formatter: "json"}) do
+ {:error,
+ %{
+ "result" => "error",
+ "message" => "Target queue does not have a message at that position"
+ }}
+ end
+ def output({:error, error}, %{formatter: "json"}) do
+ {:error,
+ %{
+ "result" => "error",
+ "message" => "Failed to perform the operation: #{error}"
+ }}
+ end
+ def output({:error, :not_found}, %{vhost: vhost}) do
+ {:error, "Target queue was not found in virtual host '#{vhost}'"}
+ end
+ def output({:error, :no_message_at_pos}, _) do
+ {:error, "Target queue does not have a message at that position"}
+ end
+ def output({:ok, msg}, %{formatter: "json"}) do
+ {:ok, %{"result" => "ok", "message" => Enum.into(msg, %{})}}
+ end
+ def output({:ok, msg}, _) do
+ res = Enum.map(msg, fn {k,v} -> [{"keys", k}, {"values", v}] end)
+ {:stream, res}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.PrettyTable
+
+ def usage() do
+ "peek [--vhost <vhost>] <queue> <position>"
+ end
+
+ def usage_additional do
+ [
+ ["<queue>", "Name of the queue",
+ "<position>", "Position in the queue, starts at 1"]
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def description(), do: "Peeks at the given position of a quorum queue"
+
+ def banner([name, pos], %{node: node_name}),
+ do: "Peeking at quorum queue #{name} at position #{pos} on node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/quorum_status_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/quorum_status_command.ex
new file mode 100644
index 0000000000..01a61b2536
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/quorum_status_command.ex
@@ -0,0 +1,54 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.QuorumStatusCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ def scopes(), do: [:diagnostics, :queues]
+
+ def merge_defaults(args, opts), do: {args, Map.merge(%{vhost: "/"}, opts)}
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name] = _args, %{node: node_name, vhost: vhost}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :status, [vhost, name]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot get quorum status of a classic queue"}
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.PrettyTable
+
+ def usage() do
+ "quorum_status [--vhost <vhost>] <queue>"
+ end
+
+ def usage_additional do
+ [
+ ["<queue>", "Name of the queue"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section(), do: :observability_and_health_checks
+
+ def description(), do: "Displays quorum status of a quorum queue"
+
+ def banner([name], %{node: node_name}),
+ do: "Status of quorum queue #{name} on node #{node_name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/rebalance_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/rebalance_command.ex
new file mode 100644
index 0000000000..1416a7c570
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/rebalance_command.ex
@@ -0,0 +1,84 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.RebalanceCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ use RabbitMQ.CLI.DefaultOutput
+
+ @known_types [
+ "all",
+ "classic",
+ "quorum"
+ ]
+
+ defp default_opts, do: %{vhost_pattern: ".*",
+ queue_pattern: ".*"}
+
+ def switches(),
+ do: [
+ vhost_pattern: :string,
+ queue_pattern: :string
+ ]
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(default_opts(), opts)}
+ end
+
+ def validate([], _) do
+ {:validation_failure, :not_enough_args}
+ end
+ def validate(args, _) when length(args) > 1 do
+ {:validation_failure, :too_many_args}
+ end
+
+ def validate([type], _) do
+ case Enum.member?(@known_types, type) do
+ true ->
+ :ok
+
+ false ->
+ {:error, "type #{type} is not supported. Try one of all, classic, quorum."}
+ end
+ end
+
+ def run([type], %{node: node_name,
+ vhost_pattern: vhost_pat,
+ queue_pattern: queue_pat}) do
+ arg = String.to_atom(type)
+ :rabbit_misc.rpc_call(node_name, :rabbit_amqqueue, :rebalance, [arg, vhost_pat, queue_pat])
+ end
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.PrettyTable
+
+ def usage, do: "rebalance < all | classic | quorum > [--vhost-pattern <pattern>] [--queue-pattern <pattern>]"
+
+ def usage_additional do
+ [
+ ["<type>", "queue type, must be one of: all, classic, quorum"],
+ ["--queue-pattern <pattern>", "regular expression to match queue names"],
+ ["--vhost-pattern <pattern>", "regular expression to match virtual host names"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section, do: :cluster_management
+
+ def description, do: "Rebalances queues."
+
+ def banner([type], _) do
+ "Rebalancing #{type} queues..."
+ end
+
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/reclaim_quorum_memory_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/reclaim_quorum_memory_command.ex
new file mode 100644
index 0000000000..3452cc8741
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/reclaim_quorum_memory_command.ex
@@ -0,0 +1,69 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.ReclaimQuorumMemoryCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+ def scopes(), do: [:diagnostics, :queues]
+
+ use RabbitMQ.CLI.Core.MergesDefaultVirtualHost
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name] = _args, %{node: node_name, vhost: vhost}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :reclaim_memory, [vhost, name]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "This operation is not applicable to classic queues"}
+
+ other ->
+ other
+ end
+ end
+
+ def output({:error, :not_found}, %{vhost: vhost, formatter: "json"}) do
+ {:error,
+ %{
+ "result" => "error",
+ "message" => "Target queue was not found in virtual host '#{vhost}'"
+ }}
+ end
+ def output({:error, error}, %{formatter: "json"}) do
+ {:error,
+ %{
+ "result" => "error",
+ "message" => "Failed to perform the operation: #{error}"
+ }}
+ end
+ def output({:error, :not_found}, %{vhost: vhost}) do
+ {:error, "Target queue was not found in virtual host '#{vhost}'"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage() do
+ "reclaim_quorum_memory [--vhost <vhost>] <quorum queue>"
+ end
+
+ def usage_additional do
+ [
+ ["<queue>", "Name of the quorum queue"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues(),
+ DocGuide.memory_use()
+ ]
+ end
+
+ def help_section(), do: :operations
+
+ def description(), do: "Flushes quorum queue processes WAL, performs a full sweep GC on all of its local Erlang processes"
+
+ def banner([name], %{}),
+ do: "Will flush Raft WAL of quorum queue #{name} ..."
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/shrink_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/shrink_command.ex
new file mode 100644
index 0000000000..1bff2b9a1c
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/queues/commands/shrink_command.ex
@@ -0,0 +1,97 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Queues.Commands.ShrinkCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def switches() do
+ [errors_only: :boolean]
+ end
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{errors_only: false}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([node], %{node: node_name, errors_only: errs}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :shrink_all, [to_atom(node)]) do
+ {:error, _} = error -> error;
+ {:badrpc, _} = error -> error;
+ results when errs ->
+ for {{:resource, vhost, _kind, name}, {:error, _, _} = res} <- results,
+ do: [{:vhost, vhost},
+ {:name, name},
+ {:size, format_size(res)},
+ {:result, format_result(res)}]
+ results ->
+ for {{:resource, vhost, _kind, name}, res} <- results,
+ do: [{:vhost, vhost},
+ {:name, name},
+ {:size, format_size(res)},
+ {:result, format_result(res)}]
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def formatter(), do: RabbitMQ.CLI.Formatters.Table
+
+ def banner([node], _) do
+ "Shrinking quorum queues on #{node}..."
+ end
+
+ def usage, do: "shrink <node> [--errors-only]"
+
+ def usage_additional() do
+ [
+ ["<node>", "node name to remove replicas from"],
+ ["--errors-only", "only list queues which reported an error"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues()
+ ]
+ end
+
+ def help_section, do: :cluster_management
+
+ def description, do: "Shrinks quorum queue clusters by removing any members (replicas) on the given node."
+
+ #
+ # Implementation
+ #
+
+ defp format_size({:ok, size}) do
+ size
+ end
+ defp format_size({:error, _size, :timeout}) do
+ # the actual size is uncertain here
+ "?"
+ end
+ defp format_size({:error, size, _}) do
+ size
+ end
+
+ defp format_result({:ok, _size}) do
+ "ok"
+ end
+ defp format_result({:error, _size, :last_node}) do
+ "error: the last node cannot be removed"
+ end
+ defp format_result({:error, _size, :timeout}) do
+ "error: the operation timed out and may not have been completed"
+ end
+ defp format_result({:error, _size, err}) do
+ :io.format "error: ~W", [err, 10]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/add_replica_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/add_replica_command.ex
new file mode 100644
index 0000000000..8dc6da0281
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/add_replica_command.ex
@@ -0,0 +1,64 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Streams.Commands.AddReplicaCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, node] = _args, %{vhost: vhost, node: node_name}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_stream_queue, :add_replica, [
+ vhost,
+ name,
+ to_atom(node)
+ ]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot add replicas to a classic queue"}
+
+ {:error, :quorum_queue_not_supported} ->
+ {:error, "Cannot add replicas to a quorum queue"}
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "add_replica [--vhost <vhost>] <queue> <node>"
+
+ def usage_additional do
+ [
+ ["<queue>", "stream queue name"],
+ ["<node>", "node to add a new replica on"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.stream_queues()
+ ]
+ end
+
+ def help_section, do: :replication
+
+ def description, do: "Adds a stream queue replica on the given node."
+
+ def banner([name, node], _) do
+ [
+ "Adding a replica for queue #{name} on node #{node}..."
+ ]
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/delete_replica_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/delete_replica_command.ex
new file mode 100644
index 0000000000..ca15282949
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/delete_replica_command.ex
@@ -0,0 +1,61 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Streams.Commands.DeleteReplicaCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+ import RabbitMQ.CLI.Core.DataCoercion
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts) do
+ {args, Map.merge(%{vhost: "/"}, opts)}
+ end
+
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, node] = _args, %{vhost: vhost, node: node_name}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_stream_queue, :delete_replica, [
+ vhost,
+ name,
+ to_atom(node)
+ ]) do
+ {:error, :classic_queue_not_supported} ->
+ {:error, "Cannot delete replicas from a classic queue"}
+
+ {:error, :quorum_queue_not_supported} ->
+ {:error, "Cannot delete replicas from a quorum queue"}
+
+ other ->
+ other
+ end
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "delete_replica [--vhost <vhost>] <queue> <node>"
+
+ def usage_additional do
+ [
+ ["<queue>", "stream queue name"],
+ ["<node>", "node to remove a replica from"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.stream_queues()
+ ]
+ end
+
+ def help_section, do: :replication
+
+ def description, do: "Removes a stream queue replica on the given node."
+
+ def banner([name, node], _) do
+ "Removing a replica of queue #{name} on node #{node}..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/set_stream_retention_policy_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/set_stream_retention_policy_command.ex
new file mode 100644
index 0000000000..ee89a1ec57
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/streams/commands/set_stream_retention_policy_command.ex
@@ -0,0 +1,58 @@
+## The contents of this file are subject to the Mozilla Public License
+## Version 1.1 (the "License"); you may not use this file except in
+## compliance with the License. You may obtain a copy of the License
+## at https://www.mozilla.org/MPL/
+##
+## Software distributed under the License is distributed on an "AS IS"
+## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+## the License for the specific language governing rights and
+## limitations under the License.
+##
+## The Original Code is RabbitMQ.
+##
+## The Initial Developer of the Original Code is GoPivotal, Inc.
+## Copyright (c) 2007-2020 Pivotal Software, Inc. All rights reserved.
+
+defmodule RabbitMQ.CLI.Streams.Commands.SetStreamRetentionPolicyCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ def merge_defaults(args, opts), do: {args, Map.merge(%{vhost: "/"}, opts)}
+
+ use RabbitMQ.CLI.Core.AcceptsTwoPositionalArguments
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+
+ def run([name, retention_policy], %{node: node_name, vhost: vhost}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_stream_queue, :set_retention_policy, [
+ name,
+ vhost,
+ retention_policy
+ ])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def banner([name, retention_policy], _) do
+ "Setting retention policy of stream queue #{name} to #{retention_policy} ..."
+ end
+
+ def usage, do: "set_stream_retention_policy [--vhost <vhost>] <name> <policy>"
+
+ def usage_additional() do
+ [
+ ["<name>", "stream queue name"],
+ ["<policy>", "retention policy"]
+ ]
+ end
+
+ def usage_doc_guides() do
+ [
+ DocGuide.stream_queues()
+ ]
+ end
+
+ def help_section(), do: :policies
+
+ def description(), do: "Sets the retention policy of a stream queue"
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/time_unit.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/time_unit.ex
new file mode 100644
index 0000000000..fa08c4befe
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/time_unit.ex
@@ -0,0 +1,48 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.TimeUnit do
+ require MapSet
+
+ @days_seconds 86400
+ @weeks_seconds @days_seconds * 7
+ @months_seconds @days_seconds * (365 / 12)
+ @years_seconds @days_seconds * 365
+
+ def known_units() do
+ MapSet.new([
+ "days",
+ "weeks",
+ "months",
+ "years"
+ ])
+ end
+
+ def convert(time, unit) do
+ do_convert(time, String.downcase(unit))
+ end
+
+ def known_unit?(val) do
+ MapSet.member?(known_units(), String.downcase(val))
+ end
+
+ defp do_convert(time, "days") do
+ time * @days_seconds
+ end
+
+ defp do_convert(time, "weeks") do
+ time * @weeks_seconds
+ end
+
+ defp do_convert(time, "months") do
+ time * @months_seconds
+ end
+
+ defp do_convert(time, "years") do
+ time * @years_seconds
+ end
+
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_quorum_plus_one_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_quorum_plus_one_command.ex
new file mode 100644
index 0000000000..ca00ddbbb7
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_quorum_plus_one_command.ex
@@ -0,0 +1,65 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Upgrade.Commands.AwaitOnlineQuorumPlusOneCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 120_000
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ val -> val
+ end
+
+ {args, Map.put(opts, :timeout, timeout)}
+ end
+
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ rpc_timeout = timeout + 500
+ case :rabbit_misc.rpc_call(node_name, :rabbit_upgrade_preparation, :await_online_quorum_plus_one, [timeout], rpc_timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ {:badrpc, _} = err -> err
+
+ true -> :ok
+ false -> {:error, "time is up, no quorum + 1 online replicas came online for at least some quorum queues"}
+ end
+ end
+
+ def output({:error, msg}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => msg}}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "await_online_quorum_plus_one"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.quorum_queues(),
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section, do: :upgrade
+
+ def description() do
+ "Waits for all quorum queues to have an above minimum online quorum. " <>
+ "This makes sure that no queues would lose their quorum if the target node is shut down"
+ end
+
+ def banner([], %{timeout: timeout}) do
+ "Will wait for a quorum + 1 of nodes to be online for all quorum queues for #{round(timeout/1000)} seconds..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_synchronized_mirror_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_synchronized_mirror_command.ex
new file mode 100644
index 0000000000..d6fb40bad2
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/await_online_synchronized_mirror_command.ex
@@ -0,0 +1,65 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Upgrade.Commands.AwaitOnlineSynchronizedMirrorCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ @default_timeout 120_000
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def merge_defaults(args, opts) do
+ timeout =
+ case opts[:timeout] do
+ nil -> @default_timeout
+ :infinity -> @default_timeout
+ val -> val
+ end
+
+ {args, Map.put(opts, :timeout, timeout)}
+ end
+
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ rpc_timeout = timeout + 500
+ case :rabbit_misc.rpc_call(node_name, :rabbit_upgrade_preparation, :await_online_synchronised_mirrors, [timeout], rpc_timeout) do
+ {:error, _} = err -> err
+ {:error, _, _} = err -> err
+ {:badrpc, _} = err -> err
+
+ true -> :ok
+ false -> {:error, "time is up, no synchronised mirror came online for at least some classic mirrored queues"}
+ end
+ end
+
+ def output({:error, msg}, %{node: node_name, formatter: "json"}) do
+ {:error, %{"result" => "error", "node" => node_name, "message" => msg}}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "await_online_synchronized_mirror"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.mirroring(),
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section, do: :upgrade
+
+ def description() do
+ "Waits for all classic mirrored queues hosted on the target node to have at least one synchronized mirror online. " <>
+ "This makes sure that if target node is shut down, there will be an up-to-date mirror to promote."
+ end
+
+ def banner([], %{timeout: timeout}) do
+ "Will wait for a synchronised mirror be online for all classic mirrored queues for #{round(timeout/1000)} seconds..."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/drain_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/drain_command.ex
new file mode 100644
index 0000000000..c6d2fc86eb
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/drain_command.ex
@@ -0,0 +1,54 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Upgrade.Commands.DrainCommand do
+ @moduledoc """
+ Puts the node in maintenance mode. Such node would not accept any
+ new client connections, closes the connections it previously had,
+ transfers leadership of locally hosted queues, and will not be considered
+ for primary queue replica placement.
+
+ This command is meant to be used when automating upgrades.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_maintenance, :drain, [], timeout) do
+ # Server does not support maintenance mode
+ {:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
+ {:badrpc, _} = err -> err
+ other -> other
+ end
+ end
+
+ def output({:error, :unsupported}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "Maintenance mode is not supported by node #{node_name}"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "drain"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section(), do: :upgrade
+
+ def description(), do: "Puts the node in maintenance mode. Such nodes will not serve any client traffic or host any primary queue replicas"
+
+ def banner(_, %{node: node_name}) do
+ "Will put node #{node_name} into maintenance mode. "
+ <> "The node will no longer serve any client traffic!"
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/post_upgrade_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/post_upgrade_command.ex
new file mode 100644
index 0000000000..76453ce9f3
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/post_upgrade_command.ex
@@ -0,0 +1,39 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Upgrade.Commands.PostUpgradeCommand do
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name}) do
+ :rabbit_misc.rpc_call(node_name, :rabbit_amqqueue, :rebalance, [:all, ".*", ".*"])
+ end
+
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "post_upgrade"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section, do: :upgrade
+
+ def description, do: "Runs post-upgrade tasks"
+
+ def banner([], _) do
+ "Executing post upgrade tasks...\n" <>
+ "Rebalancing queue masters..."
+ end
+
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/revive_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/revive_command.ex
new file mode 100644
index 0000000000..a594561a55
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/upgrade/commands/revive_command.ex
@@ -0,0 +1,56 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQ.CLI.Upgrade.Commands.ReviveCommand do
+ @moduledoc """
+ Puts the node out of maintenance and into regular operating mode.
+ Such nodes will again serve client traffic and be considered for
+ primary queue replica placement.
+
+ A node will automatically go into regular operational mode
+ after a restart.
+
+ This command is meant to be used when automating upgrades.
+ """
+
+ @behaviour RabbitMQ.CLI.CommandBehaviour
+
+ alias RabbitMQ.CLI.Core.DocGuide
+
+ use RabbitMQ.CLI.Core.MergesNoDefaults
+ use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
+
+ def run([], %{node: node_name, timeout: timeout}) do
+ case :rabbit_misc.rpc_call(node_name, :rabbit_maintenance, :revive, [], timeout) do
+ # Server does not support maintenance mode
+ {:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
+ {:badrpc, _} = err -> err
+ other -> other
+ end
+ end
+
+ def output({:error, :unsupported}, %{node: node_name}) do
+ {:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage, "Maintenance mode is not supported by node #{node_name}"}
+ end
+ use RabbitMQ.CLI.DefaultOutput
+
+ def usage, do: "revive"
+
+ def usage_doc_guides() do
+ [
+ DocGuide.upgrade()
+ ]
+ end
+
+ def help_section(), do: :upgrade
+
+ def description(), do: "Puts the node out of maintenance and into regular operating mode. Such nodes will again serve client traffic and host primary queue replicas"
+
+ def banner(_, %{node: node_name}) do
+ "Will put node #{node_name} back into regular operating mode. "
+ <> "The node will again serve client traffic and host primary queue replicas."
+ end
+end
diff --git a/deps/rabbitmq_cli/lib/rabbitmqctl.ex b/deps/rabbitmq_cli/lib/rabbitmqctl.ex
new file mode 100644
index 0000000000..42a0f20434
--- /dev/null
+++ b/deps/rabbitmq_cli/lib/rabbitmqctl.ex
@@ -0,0 +1,620 @@
+## This Source Code Form is subject to the terms of the Mozilla Public
+## License, v. 2.0. If a copy of the MPL was not distributed with this
+## file, You can obtain one at https://mozilla.org/MPL/2.0/.
+##
+## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+defmodule RabbitMQCtl do
+ alias RabbitMQ.CLI.Core.{
+ CommandModules,
+ Config,
+ Distribution,
+ ExitCodes,
+ Helpers,
+ Output,
+ Parser
+ }
+ alias RabbitMQ.CLI.{CommandBehaviour, FormatterBehaviour}
+ alias RabbitMQ.CLI.Ctl.Commands.HelpCommand
+
+ # Enable unit tests for private functions
+ @compile if Mix.env() == :test, do: :export_all
+
+ @type options() :: map()
+ @type command_result() :: {:error, ExitCodes.exit_code(), term()} | term()
+
+ def main(["--auto-complete" | []]) do
+ handle_shutdown(:ok)
+ end
+
+ def main(unparsed_command) do
+ exec_command(unparsed_command, &process_output/3)
+ |> handle_shutdown
+ end
+
+ def exec_command([] = unparsed_command, _) do
+ {_args, parsed_options, _} = Parser.parse_global(unparsed_command)
+
+ # this invocation is considered to be invalid. curl and grep do the
+ # same thing.
+ {:error, ExitCodes.exit_usage(), Enum.join(HelpCommand.all_usage(parsed_options), "")};
+ end
+ def exec_command(["--help"] = unparsed_command, _) do
+ {_args, parsed_options, _} = Parser.parse_global(unparsed_command)
+
+ # the user asked for --help and we are displaying it to her,
+ # reporting a success
+ {:ok, ExitCodes.exit_ok(), Enum.join(HelpCommand.all_usage(parsed_options), "")};
+ end
+ def exec_command(["--version"] = _unparsed_command, opts) do
+ # rewrite `--version` as `version`
+ exec_command(["version"], opts)
+ end
+ def exec_command(["--auto-complete" | args], opts) do
+ # rewrite `--auto-complete` as `autocomplete`
+ exec_command(["autocomplete" | args], opts)
+ end
+ def exec_command(unparsed_command, output_fun) do
+ {command, command_name, arguments, parsed_options, invalid} = Parser.parse(unparsed_command)
+
+ case {command, invalid} do
+ {:no_command, _} ->
+ command_not_found_string =
+ case command_name do
+ "" -> ""
+ _ -> "\nCommand '#{command_name}' not found. \n"
+ end
+
+ usage_string =
+ command_not_found_string <>
+ Enum.join(HelpCommand.all_usage(parsed_options), "")
+
+ {:error, ExitCodes.exit_usage(), usage_string}
+
+ {{:suggest, suggested}, _} ->
+ suggest_message =
+ "\nCommand '#{command_name}' not found. \n" <>
+ "Did you mean '#{suggested}'? \n"
+
+ {:error, ExitCodes.exit_usage(), suggest_message}
+
+ {_, [_ | _]} ->
+ argument_validation_error_output(
+ {:bad_option, invalid},
+ command,
+ unparsed_command,
+ parsed_options
+ )
+
+ _ ->
+ options = parsed_options |> merge_all_defaults |> normalise_options
+
+ try do
+ do_exec_parsed_command(unparsed_command, output_fun, arguments, command, options)
+ catch error_type, error ->
+ maybe_print_stacktrace(error_type, error, __STACKTRACE__, options)
+ format_error(error, options, command)
+ end
+ end
+ end
+
+ def do_exec_parsed_command(unparsed_command, output_fun, arguments, command, options) do
+ case options[:help] do
+ true ->
+ {:ok, ExitCodes.exit_ok(), HelpCommand.command_usage(command, options)};
+ _ ->
+ {arguments, options} = command.merge_defaults(arguments, options)
+
+ maybe_with_distribution(command, options, fn ->
+ # rabbitmq/rabbitmq-cli#278
+ case Helpers.normalise_node_option(options) do
+ {:error, _} = err ->
+ format_error(err, options, command)
+ {:ok, options} ->
+ # The code below implements a tiny decision tree that has
+ # to do with CLI argument and environment state validation.
+ case command.validate(arguments, options) do
+ :ok ->
+ # then optionally validate execution environment
+ case CommandBehaviour.validate_execution_environment(command, arguments, options) do
+ :ok ->
+ result = proceed_to_execution(command, arguments, options)
+ handle_command_output(result, command, options, output_fun)
+
+ {:validation_failure, err} ->
+ environment_validation_error_output(err, command, unparsed_command, options)
+
+ {:error, _} = err ->
+ format_error(err, options, command)
+ end
+
+ {:validation_failure, err} ->
+ argument_validation_error_output(err, command, unparsed_command, options)
+
+ {:error, _} = err ->
+ format_error(err, options, command)
+ end
+ end
+ end)
+ end
+ end
+
+ defp proceed_to_execution(command, arguments, options) do
+ maybe_print_banner(command, arguments, options)
+ maybe_run_command(command, arguments, options)
+ end
+
+ defp maybe_run_command(_, _, %{dry_run: true}) do
+ :ok
+ end
+
+ defp maybe_run_command(command, arguments, options) do
+ try do
+ command.run(arguments, options) |> command.output(options)
+ catch error_type, error ->
+ maybe_print_stacktrace(error_type, error, __STACKTRACE__, options)
+ format_error(error, options, command)
+ end
+ end
+
+ def maybe_print_stacktrace(error_type, :undef = error, stacktrace, _opts) do
+ do_print_stacktrace(error_type, error, stacktrace)
+ end
+ def maybe_print_stacktrace(error_type, :function_clause = error, stacktrace, _opts) do
+ do_print_stacktrace(error_type, error, stacktrace)
+ end
+ def maybe_print_stacktrace(error_type, :badarg = error, stacktrace, _opts) do
+ do_print_stacktrace(error_type, error, stacktrace)
+ end
+ def maybe_print_stacktrace(error_type, {:case_clause, _val} = error, stacktrace, _opts) do
+ do_print_stacktrace(error_type, error, stacktrace)
+ end
+ def maybe_print_stacktrace(error_type, error, stacktrace, %{print_stacktrace: true}) do
+ do_print_stacktrace(error_type, error, stacktrace)
+ end
+ def maybe_print_stacktrace(_error_type, _error, _stacktrace, %{print_stacktrace: false}) do
+ nil
+ end
+ def maybe_print_stacktrace(_error_type, _error, _stacktrace, _opts) do
+ nil
+ end
+
+ defp do_print_stacktrace(error_type, error, stacktrace) do
+ IO.puts("Stack trace: \n")
+ IO.puts(Exception.format(error_type, error, stacktrace))
+ end
+
+ def handle_command_output(output, command, options, output_fun) do
+ case output do
+ {:error, _, _} = err ->
+ format_error(err, options, command)
+
+ {:error, _} = err ->
+ format_error(err, options, command)
+
+ _ ->
+ output_fun.(output, command, options)
+ end
+ end
+
+ defp process_output(output, command, options) do
+ formatter = Config.get_formatter(command, options)
+ printer = Config.get_printer(command, options)
+
+ output
+ |> Output.format_output(formatter, options)
+ |> Output.print_output(printer, options)
+ |> case do
+ {:error, _} = err -> format_error(err, options, command)
+ other -> other
+ end
+ end
+
+ defp output_device(exit_code) do
+ case exit_code == ExitCodes.exit_ok() do
+ true -> :stdio
+ false -> :stderr
+ end
+ end
+
+ defp handle_shutdown({:error, exit_code, nil}) do
+ exit_program(exit_code)
+ end
+
+ defp handle_shutdown({_, exit_code, output}) do
+ device = output_device(exit_code)
+
+ for line <- List.flatten([output]) do
+ IO.puts(device, Helpers.string_or_inspect(line))
+ end
+
+ exit_program(exit_code)
+ end
+
+ defp handle_shutdown(_) do
+ exit_program(ExitCodes.exit_ok())
+ end
+
+ def merge_all_defaults(%{} = options) do
+ options
+ |> merge_defaults_node
+ |> merge_defaults_timeout
+ |> merge_defaults_longnames
+ end
+
+ defp merge_defaults_node(%{} = opts) do
+ longnames_opt = Config.get_option(:longnames, opts)
+ try do
+ default_rabbit_nodename = Helpers.get_rabbit_hostname(longnames_opt)
+ Map.merge(%{node: default_rabbit_nodename}, opts)
+ catch _error_type, _err ->
+ opts
+ end
+ end
+
+ defp merge_defaults_timeout(%{} = opts), do: Map.merge(%{timeout: :infinity}, opts)
+
+ defp merge_defaults_longnames(%{} = opts), do: Map.merge(%{longnames: false}, opts)
+
+ defp normalise_options(opts) do
+ opts |> normalise_timeout
+ end
+
+ defp normalise_timeout(%{timeout: timeout} = opts)
+ when is_integer(timeout) do
+ Map.put(opts, :timeout, timeout * 1000)
+ end
+
+ defp normalise_timeout(opts) do
+ opts
+ end
+
+ # Suppress banner if --quiet option is provided
+ defp maybe_print_banner(_, _, %{quiet: true}) do
+ nil
+ end
+
+ # Suppress banner if --silent option is provided
+ defp maybe_print_banner(_, _, %{silent: true}) do
+ nil
+ end
+
+ defp maybe_print_banner(command, args, opts) do
+ # Suppress banner if a machine-readable formatter is used
+ formatter = Map.get(opts, :formatter)
+ case FormatterBehaviour.machine_readable?(formatter) do
+ true -> nil
+ false ->
+ case command.banner(args, opts) do
+ nil ->
+ nil
+
+ banner ->
+ case banner do
+ list when is_list(list) ->
+ for line <- list, do: IO.puts(line)
+
+ binary when is_binary(binary) ->
+ IO.puts(binary)
+ end
+ end
+ end
+ end
+
+ def argument_validation_error_output(err_detail, command, unparsed_command, options) do
+ err = format_validation_error(err_detail)
+
+ base_error =
+ "Error (argument validation): #{err}\nArguments given:\n\t#{
+ unparsed_command |> Enum.join(" ")
+ }"
+
+ validation_error_output(err_detail, base_error, command, options)
+ end
+
+ def environment_validation_error_output(err_detail, command, unparsed_command, options) do
+ err = format_validation_error(err_detail)
+ base_error = "Error: #{err}\nArguments given:\n\t#{unparsed_command |> Enum.join(" ")}"
+ validation_error_output(err_detail, base_error, command, options)
+ end
+
+ defp validation_error_output(err_detail, base_error, command, options) do
+ usage = HelpCommand.base_usage(command, options)
+ message = base_error <> "\n" <> usage
+ {:error, ExitCodes.exit_code_for({:validation_failure, err_detail}), message}
+ end
+
+ defp format_validation_error(:not_enough_args), do: "not enough arguments."
+ defp format_validation_error({:not_enough_args, detail}), do: "not enough arguments: #{detail}"
+ defp format_validation_error(:too_many_args), do: "too many arguments."
+ defp format_validation_error({:too_many_args, detail}), do: "too many arguments: #{detail}"
+ defp format_validation_error(:bad_argument), do: "Bad argument."
+ defp format_validation_error({:bad_argument, detail}), do: "Bad argument: #{detail}"
+
+ defp format_validation_error({:unsupported_target, details}) do
+ details
+ end
+
+ defp format_validation_error({:bad_option, opts}) do
+ header = "Invalid options for this command:"
+
+ Enum.join(
+ [
+ header
+ | for {key, val} <- opts do
+ "#{key} : #{val}"
+ end
+ ],
+ "\n"
+ )
+ end
+
+ defp format_validation_error({:bad_info_key, keys}),
+ do: "Info key(s) #{Enum.join(keys, ",")} are not supported"
+
+ defp format_validation_error(:rabbit_app_is_stopped),
+ do:
+ "this command requires the 'rabbit' app to be running on the target node. Start it with 'rabbitmqctl start_app'."
+
+ defp format_validation_error(:rabbit_app_is_running),
+ do:
+ "this command requires the 'rabbit' app to be stopped on the target node. Stop it with 'rabbitmqctl stop_app'."
+
+ defp format_validation_error(:node_running),
+ do: "this command requires the target node to be stopped."
+
+ defp format_validation_error(:node_not_running),
+ do: "this command requires the target node to be running."
+
+ defp format_validation_error(:unsupported_formatter),
+ do: "the requested formatter is not supported by this command"
+
+ defp format_validation_error(err), do: inspect(err)
+
+ defp exit_program(code) do
+ :net_kernel.stop()
+ exit({:shutdown, code})
+ end
+
+ defp format_error({:error, {:node_name, :hostname_not_allowed}}, _, _) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Unsupported node name: hostname is invalid (possibly contains unsupported characters).\nIf using FQDN node names, use the -l / --longnames argument."}
+ end
+ defp format_error({:error, {:node_name, :invalid_node_name_head}}, _, _) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Unsupported node name: node name head (the part before the @) is invalid. Only alphanumerics, _ and - characters are allowed.\nIf using FQDN node names, use the -l / --longnames argument"}
+ end
+ defp format_error({:error, {:node_name, err_reason} = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+ node = opts[:node] || "(failed to parse or compute default value)"
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} failed due to invalid node name (node: #{node}, reason: #{err_reason}).\nIf using FQDN node names, use the -l / --longnames argument"}
+ end
+
+ defp format_error({:error, {:badrpc_multi, :nodedown, [node | _]} = result}, opts, _) do
+ diagnostics = get_node_diagnostics(node)
+
+ {:error, ExitCodes.exit_code_for(result),
+ badrpc_error_message_header(node, opts) <> diagnostics}
+ end
+
+ defp format_error({:error, {:badrpc_multi, :timeout, [node | _]} = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} on node #{node} timed out. Timeout value used: #{opts[:timeout]}"}
+ end
+
+ defp format_error({:error, {:badrpc, :nodedown} = result}, opts, _) do
+ diagnostics = get_node_diagnostics(opts[:node])
+
+ {:error, ExitCodes.exit_code_for(result),
+ badrpc_error_message_header(opts[:node], opts) <> diagnostics}
+ end
+
+ defp format_error({:error, {:badrpc, :timeout} = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} on node #{opts[:node]} timed out. Timeout value used: #{
+ opts[:timeout]
+ }"}
+ end
+
+ defp format_error({:error, {:badrpc, {:timeout, to}} = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} on node #{opts[:node]} timed out. Timeout value used: #{to}"}
+ end
+
+ defp format_error({:error, {:badrpc, {:timeout, to, warning}}}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for({:timeout, to}),
+ "Error: operation #{op} on node #{opts[:node]} timed out. Timeout value used: #{to}. #{
+ warning
+ }"}
+ end
+
+ defp format_error({:error, {:no_such_vhost, vhost} = result}, _opts, _) do
+ {:error, ExitCodes.exit_code_for(result), "Virtual host '#{vhost}' does not exist"}
+ end
+
+ defp format_error({:error, {:incompatible_version, local_version, remote_version} = result}, _opts, _) do
+ {:error, ExitCodes.exit_code_for(result), "Detected potential version incompatibility. CLI tool version: #{local_version}, server: #{remote_version}"}
+ end
+
+ defp format_error({:error, {:timeout, to} = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} on node #{opts[:node]} timed out. Timeout value used: #{to}"}
+ end
+
+ defp format_error({:error, :timeout = result}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(result),
+ "Error: operation #{op} on node #{opts[:node]} timed out. Timeout value used: #{opts[:timeout]}"}
+ end
+
+ defp format_error({:error, :timeout, msg}, opts, module) do
+ op = CommandModules.module_to_command(module)
+
+ {:error, ExitCodes.exit_code_for(:timeout),
+ "Error: operation #{op} on node #{opts[:node]} timed out: #{msg}. Timeout value used: #{opts[:timeout]}"}
+ end
+
+ # Plugins
+ defp format_error({:error, {:enabled_plugins_mismatch, cli_path, node_path}}, opts, _module) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{cli_path}: target node #{opts[:node]} uses a different path (#{
+ node_path
+ })"}
+ end
+
+ defp format_error({:error, {:cannot_read_enabled_plugins_file, path, :eacces}}, _opts, _module) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not read enabled plugins file at #{path}: the file does not exist or permission was denied (EACCES)"}
+ end
+
+ defp format_error({:error, {:cannot_read_enabled_plugins_file, path, :enoent}}, _opts, _module) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not read enabled plugins file at #{path}: the file does not exist (ENOENT)"}
+ end
+
+ defp format_error({:error, {:cannot_write_enabled_plugins_file, path, :eacces}}, _opts, _module) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{path}: the file does not exist or permission was denied (EACCES)"}
+ end
+
+ defp format_error({:error, {:cannot_write_enabled_plugins_file, path, :enoent}}, _opts, _module) do
+ {:error, ExitCodes.exit_dataerr(),
+ "Could not update enabled plugins file at #{path}: the file does not exist (ENOENT)"}
+ end
+
+ # Special case health checks. This makes it easier to change
+ # output of all health checks at once.
+ defp format_error({:error, :check_failed}, %{formatter: "json"}, _) do
+ {:error, ExitCodes.exit_unavailable(), nil}
+ end
+ defp format_error({:error, :check_failed}, _, _) do
+ {:error, ExitCodes.exit_unavailable(), nil}
+ end
+ defp format_error({:error, :check_failed, err}, %{formatter: "json"}, _) when is_map(err) do
+ {:ok, res} = JSON.encode(err)
+ {:error, ExitCodes.exit_unavailable(), res}
+ end
+ defp format_error({:error, :check_failed, err}, %{formatter: "json"}, _) do
+ {:error, ExitCodes.exit_unavailable(), err}
+ end
+ defp format_error({:error, :check_failed, err}, _, _) do
+ {:error, ExitCodes.exit_unavailable(), err}
+ end
+
+ defp format_error({:error, nil}, _, _) do
+ # the command intends to produce no output, e.g. a return code
+ # is sufficient
+ {:error, ExitCodes.exit_unavailable(), nil}
+ end
+
+ # Catch all clauses
+ defp format_error({:error, err}, %{formatter: "json"}, _) when is_map(err) do
+ {:ok, res} = JSON.encode(err)
+ {:error, ExitCodes.exit_unavailable(), res}
+ end
+ defp format_error({:error, exit_code, err}, %{formatter: "json"}, _) when is_map(err) do
+ {:ok, res} = JSON.encode(err)
+ {:error, exit_code, res}
+ end
+
+ defp format_error({:error, exit_code, err}, _, _) do
+ string_err = Helpers.string_or_inspect(err)
+
+ {:error, exit_code, "Error:\n#{string_err}"}
+ end
+
+ defp format_error({:error, err} = result, _, _) do
+ string_err = Helpers.string_or_inspect(err)
+
+ {:error, ExitCodes.exit_code_for(result), "Error:\n#{string_err}"}
+ end
+
+ defp format_error(error, _opts, _module) do
+ {:error, ExitCodes.exit_software(), "#{inspect(error)}\n"}
+ end
+
+ defp get_node_diagnostics(nil) do
+ "Target node is not defined"
+ end
+
+ defp get_node_diagnostics(node_name) do
+ to_string(:rabbit_nodes_common.diagnostics([node_name]))
+ end
+
+ defp badrpc_error_message_header(node, _opts) do
+ """
+ Error: unable to perform an operation on node '#{node}'. Please see diagnostics information and suggestions below.
+
+ Most common reasons for this are:
+
+ * Target node is unreachable (e.g. due to hostname resolution, TCP connection or firewall issues)
+ * CLI tool fails to authenticate with the server (e.g. due to CLI tool's Erlang cookie not matching that of the server)
+ * Target node is not running
+
+ In addition to the diagnostics info below:
+
+ * See the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more
+ * Consult server logs on node #{node}
+ * If target node is configured to use long node names, don't forget to use --longnames with CLI tools
+ """
+ end
+
+ ## Tries to enable erlang distribution, which can be configured
+ ## via distribution callback in the command as :cli, :none or {:custom, fun()}.
+ ## :cli - default rabbitmqctl node name
+ ## :none - do not start a distribution (e.g. offline command)
+ ## {:fun, fun} - run a custom function to enable distribution.
+ ## custom mode is usefult for commands which should have specific node name.
+ ## Runs code if distribution is successful, or not needed.
+ @spec maybe_with_distribution(module(), options(), (() -> command_result())) :: command_result()
+ defp maybe_with_distribution(command, options, code) do
+ try do
+ maybe_with_distribution_without_catch(command, options, code)
+ catch error_type, error ->
+ maybe_print_stacktrace(error_type, error, __STACKTRACE__, options)
+ format_error(error, options, command)
+ end
+ end
+
+ defp maybe_with_distribution_without_catch(command, options, code) do
+ distribution_type = CommandBehaviour.distribution(command, options)
+
+ case distribution_type do
+ :none ->
+ code.()
+
+ :cli ->
+ case Distribution.start(options) do
+ :ok ->
+ code.()
+
+ {:ok, _} ->
+ code.()
+
+ {:error, reason} ->
+ {:error, ExitCodes.exit_config(), "Distribution failed: #{inspect(reason)}"}
+ end
+
+ {:fun, fun} ->
+ case fun.(options) do
+ :ok ->
+ code.()
+
+ {:error, reason} ->
+ {:error, ExitCodes.exit_config(), "Distribution failed: #{inspect(reason)}"}
+ end
+ end
+ end
+end