Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion doc/appendices/command-line/traffic_ctl.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@ traffic_ctl server
This string should contain an anchored regular expression that filters the messages based on the debug tag tag.
Please refer to :ts:cv:`proxy.config.diags.debug.tags` for more information

.. option:: --append, -a

Append the specified tags to the existing debug tags instead of replacing them. This option requires
``--tags`` to be specified. The new tags will be combined with existing tags using the ``|`` separator.

.. option:: --client_ip, -c ip

Please see :ts:cv:`proxy.config.diags.debug.client_ip` for information.
Expand All @@ -547,13 +552,22 @@ traffic_ctl server
Disables logging for diagnostic messages. Equivalent to set :ts:cv:`proxy.config.diags.debug.enabled` to ``0``.


Example:
Examples:

.. code-block:: bash

# Set debug tags (replaces existing tags)
$ traffic_ctl server debug enable --tags "quic|quiche"
■ TS Runtime debug set to »ON(1)« - tags »"quic|quiche"«, client_ip »unchanged«

# Append debug tags to existing tags
$ traffic_ctl server debug enable --tags "http" --append
■ TS Runtime debug set to »ON(1)« - tags »"quic|quiche|http"«, client_ip »unchanged«

# Disable debug logging
$ traffic_ctl server debug disable
■ TS Runtime debug set to »OFF(0)«

.. _traffic-control-command-storage:

traffic_ctl storage
Expand Down
59 changes: 56 additions & 3 deletions doc/developer-guide/internal-libraries/ArgParser.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,40 @@ Example with a required group:

// User must specify either --json or --xml, but not both

Option Dependencies
-------------------

ArgParser supports option dependencies, where one option requires another option to be present.
This is useful when an option only makes sense in combination with another option.

To specify that an option requires another option, use the ``with_required()`` method immediately after
adding the option:

.. code-block:: cpp

command.add_option("--tags", "-t", "Debug tags", "", 1)
command.add_option("--append", "-a", "Append tags to existing tags")
.with_required("--tags"); // --append requires --tags to be present

When ``--append`` is used without ``--tags``, ArgParser will display an error message and exit:

.. code-block:: text

Error: Option '--append' requires '--tags' to be specified

Multiple dependencies can be specified by chaining ``with_required()`` calls:

.. code-block:: cpp

command.add_option("--verbose-append", "-V", "Verbose append mode")
.with_required("--tags")
.with_required("--append"); // requires both --tags and --append

.. Note::

The ``with_required()`` method must be called immediately after ``add_option()`` or
``add_option_to_group()``. It applies to the most recently added option.

Parsing Arguments
-----------------

Expand Down Expand Up @@ -267,10 +301,23 @@ Classes
is called under certain command, it will be added as a subcommand for the current command. For Example, :code:`command1.add_command("command2", "description")`
will make :code:`command2` a subcommand of :code:`command1`. :code:`require_commands()` is also available within :class:`Command`.

.. function:: void add_example_usage(std::string const &usage)
.. function:: Command &add_example_usage(std::string const &usage)

Add an example usage for the command to output in ``help_message``. This method can be
called multiple times to add multiple examples. Returns the Command instance for chained calls.

Example::

Add an example usage for the command to output in `help_message`.
For Example: :code:`command.add_example_usage("traffic_blabla init --path=/path/to/file")`.
command.add_example_usage("traffic_ctl server debug enable -t my_tags")
.add_example_usage("traffic_ctl server debug enable -t new_tag -a # append mode");

This will output in help:

.. code-block:: text

Example Usage:
traffic_ctl server debug enable -t my_tags
traffic_ctl server debug enable -t new_tag -a # append mode

.. function:: Command &set_default()

Expand All @@ -284,6 +331,12 @@ Classes

Add an option to a mutually exclusive group for this command.

.. function:: Command &with_required(std::string const &required_option)

Specify that the last added option requires another option to be present.
Must be called immediately after ``add_option()`` or ``add_option_to_group()``.
Returns the Command instance for chained calls.

.. class:: Arguments

:class:`Arguments` holds the parsed arguments and function to invoke.
Expand Down
18 changes: 16 additions & 2 deletions include/tscore/ArgParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ class ArgParser
std::string const &description, std::string const &envvar = "", unsigned arg_num = 0,
std::string const &default_value = "", std::string const &key = "");

/** Specify that the last added option requires another option to be present.
Must be called immediately after add_option() or add_option_to_group().
@param required_option The option that must be present (e.g., "--tags")
@return The Command instance for chained calls.
*/
Command &with_required(std::string const &required_option);

/** Two ways of adding a sub-command to current command:
@return The new sub-command instance.
*/
Expand Down Expand Up @@ -217,6 +224,8 @@ class ArgParser
void append_option_data(Arguments &ret, AP_StrVec &args, int index);
// Helper method to validate mutually exclusive groups
void validate_mutex_groups(Arguments &ret) const;
// Helper method to validate option dependencies
void validate_dependencies(Arguments &ret) const;
// The command name and help message
std::string _name;
std::string _description;
Expand All @@ -225,8 +234,8 @@ class ArgParser
unsigned _arg_num = 0;
// Stored Env variable
std::string _envvar;
// An example usage can be added for the help message
std::string _example_usage;
// Example usages can be added for the help message
std::vector<std::string> _example_usages;
// Function associated with this command
Function _f;
// look up key
Expand All @@ -248,6 +257,11 @@ class ArgParser
// Key: long option name. Value: group name
std::map<std::string, std::string> _option_to_group;

// Option dependencies: dependent_option -> list of required options
std::map<std::string, std::vector<std::string>> _option_dependencies;
// Track the last added option for with_required() chaining
std::string _last_added_option;

// require command / option for this parser
bool _command_required = false;

Expand Down
21 changes: 20 additions & 1 deletion src/traffic_ctl/CtrlCommands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -695,12 +695,31 @@ ServerCommand::server_debug()
{
// Set ATS to enable or disable debug at runtime.
const bool enable = get_parsed_arguments()->get(ENABLE_STR);
const bool append = get_parsed_arguments()->get(APPEND_STR);

// If the following is not passed as options then the request will ignore them as default values
// will be set.
const std::string tags = get_parsed_arguments()->get(TAGS_STR).value();
std::string tags = get_parsed_arguments()->get(TAGS_STR).value();
const std::string client_ip = get_parsed_arguments()->get(CLIENT_IP_STR).value();

// If append mode is enabled and tags are provided, fetch current tags and combine
if (append && !tags.empty()) {
shared::rpc::RecordLookupRequest lookup_request;
lookup_request.emplace_rec("proxy.config.diags.debug.tags", shared::rpc::NOT_REGEX, shared::rpc::CONFIG_REC_TYPES);
auto lookup_response = invoke_rpc(lookup_request);

if (!lookup_response.is_error()) {
auto const &records = lookup_response.result.as<shared::rpc::RecordLookUpResponse>();
if (!records.recordList.empty()) {
std::string current_tags = records.recordList[0].currentValue;
if (!current_tags.empty()) {
// Combine: current|new
tags = current_tags + "|" + tags;
}
}
}
}

const SetDebugServerRequest request{enable, tags, client_ip};
shared::rpc::JSONRPCResponse const &response = invoke_rpc(request);

Expand Down
1 change: 1 addition & 0 deletions src/traffic_ctl/CtrlCommands.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class ServerCommand : public CtrlCommand
static inline const std::string ENABLE_STR{"enable"};
static inline const std::string DISABLE_STR{"disable"};
static inline const std::string TAGS_STR{"tags"};
static inline const std::string APPEND_STR{"append"};
static inline const std::string CLIENT_IP_STR{"client_ip"};

static inline const std::string STATUS_STR{"status"};
Expand Down
5 changes: 4 additions & 1 deletion src/traffic_ctl/traffic_ctl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,11 @@ main([[maybe_unused]] int argc, const char **argv)
server_command.add_command("debug", "Enable/Disable ATS for diagnostic messages at runtime").require_commands();
debug_command.add_command("enable", "Enables logging for diagnostic messages at runtime", Command_Execute)
.add_option("--tags", "-t", "Debug tags", "TS_DEBUG_TAGS", 1)
.add_option("--append", "-a", "Append tags to existing tags instead of replacing")
.with_required("--tags")
.add_option("--client_ip", "-c", "Client's ip", "", 1, "")
.add_example_usage("traffic_ctl server debug enable -t my_tags -c X.X.X.X");
.add_example_usage("traffic_ctl server debug enable -t my_tags -c X.X.X.X")
.add_example_usage("traffic_ctl server debug enable -t new_tag -a # append mode");
debug_command.add_command("disable", "Disables logging for diagnostic messages at runtime", Command_Execute)
.add_example_usage("traffic_ctl server debug disable");

Expand Down
93 changes: 85 additions & 8 deletions src/tscore/ArgParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,12 @@ ArgParser::Command::help_message(std::string_view err) const
std::cout << "\nOptions ======================= Default ===== Description =============" << std::endl;
output_option();
}
// output example usage
if (!_example_usage.empty()) {
std::cout << "\nExample Usage: " << _example_usage << std::endl;
// output example usages
if (!_example_usages.empty()) {
std::cout << "\nExample Usage:" << std::endl;
for (const auto &example : _example_usages) {
std::cout << " " << example << std::endl;
}
}
// standard return code
ArgParser::do_exit(usage_return_code);
Expand Down Expand Up @@ -303,6 +306,7 @@ ArgParser::Command::add_option(std::string const &long_option, std::string const
if (short_option != "-" && !short_option.empty()) {
_option_map[short_option] = long_option;
}
_last_added_option = long_option; // track for with_required() chaining
return *this;
}

Expand Down Expand Up @@ -340,7 +344,7 @@ ArgParser::Command::add_option_to_group(std::string const &group_name, std::stri
ArgParser::do_exit(1);
}

// Add the option normally
// Add the option normally (this also sets _last_added_option)
add_option(long_option, short_option, description, envvar, arg_num, default_value, key);

// Track this option in the mutex group
Expand Down Expand Up @@ -375,7 +379,7 @@ ArgParser::Command::add_command(std::string const &cmd_name, std::string const &
ArgParser::Command &
ArgParser::Command::add_example_usage(std::string const &usage)
{
_example_usage = usage;
_example_usages.push_back(usage);
return *this;
}

Expand Down Expand Up @@ -443,11 +447,28 @@ ArgParser::Command::output_option() const
msg = msg + std::string(INDENT_ONE - msg.size(), ' ') + it.second.default_value;
}
}
if (!it.second.description.empty()) {
// Build description with dependency info if applicable
std::string desc = it.second.description;
auto dep_it = _option_dependencies.find(it.first);
if (dep_it != _option_dependencies.end() && !dep_it->second.empty()) {
if (!desc.empty()) {
desc += " ";
}
desc += "(requires";
for (size_t i = 0; i < dep_it->second.size(); ++i) {
desc += " " + dep_it->second[i];
if (i < dep_it->second.size() - 1) {
desc += ",";
}
}
desc += ")";
}

if (!desc.empty()) {
if (INDENT_TWO - static_cast<int>(msg.size()) < 0) {
std::cout << msg << "\n" << std::string(INDENT_TWO, ' ') << it.second.description << std::endl;
std::cout << msg << "\n" << std::string(INDENT_TWO, ' ') << desc << std::endl;
} else {
std::cout << msg << std::string(INDENT_TWO - msg.size(), ' ') << it.second.description << std::endl;
std::cout << msg << std::string(INDENT_TWO - msg.size(), ' ') << desc << std::endl;
}
}
}
Expand Down Expand Up @@ -574,6 +595,59 @@ ArgParser::Command::validate_mutex_groups(Arguments &ret) const
}
}

// Specify that the last added option requires another option
ArgParser::Command &
ArgParser::Command::with_required(std::string const &required_option)
{
if (_last_added_option.empty()) {
std::cerr << "Error: with_required() must be called after add_option()" << std::endl;
ArgParser::do_exit(1);
}

// Validate that required option exists
if (_option_list.find(required_option) == _option_list.end()) {
std::cerr << "Error: Required option '" << required_option << "' not found" << std::endl;
ArgParser::do_exit(1);
}

_option_dependencies[_last_added_option].push_back(required_option);

return *this;
}

// Validate option dependencies
void
ArgParser::Command::validate_dependencies(Arguments &ret) const
{
for (const auto &[dependent, required_list] : _option_dependencies) {
// Get the key for the dependent option
auto it = _option_list.find(dependent);
if (it == _option_list.end()) {
continue;
}

const std::string &dep_key = it->second.key;

// Check if dependent option was used
if (ret.get(dep_key)) {
// Dependent option was used, check all required options
for (const auto &required : required_list) {
auto req_it = _option_list.find(required);
if (req_it == _option_list.end()) {
continue;
}

const std::string &req_key = req_it->second.key;

if (!ret.get(req_key)) {
std::string error_msg = "Option '" + dependent + "' requires '" + required + "' to be specified";
help_message(error_msg); // exit with status code 64 (EX_USAGE - command line usage error)
}
}
}
}
}

// Append the args of option to parsed data. Return true if there is any option called
void
ArgParser::Command::append_option_data(Arguments &ret, AP_StrVec &args, int index)
Expand Down Expand Up @@ -694,6 +768,9 @@ ArgParser::Command::parse(Arguments &ret, AP_StrVec &args)

// Validate mutually exclusive groups
validate_mutex_groups(ret);

// Validate option dependencies
validate_dependencies(ret);
}

if (command_called) {
Expand Down
1 change: 1 addition & 0 deletions src/tscore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ if(BUILD_TESTING)
unit_tests/test_scoped_resource.cc
unit_tests/test_Version.cc
unit_tests/test_ArgParser_MutexGroup.cc
unit_tests/test_ArgParser_OptionDependencies.cc
unit_tests/test_Allocator.cc
)
target_link_libraries(
Expand Down
Loading