This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch 10.0.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit a5f56abf7b27f4464c65bcc9b0a53c1d5fc6fd2f Author: Brian Neradt <[email protected]> AuthorDate: Thu Aug 1 14:53:47 2024 -0500 remap_action_to_add: helper script for new ACL actions (#11583) This helper script converts 9.2.x @action=allow and @action=deny to @action=add_allow and @action=add_deny in a provided input remap.config filename. This prepares a 9.2.x remap.config file to be compatible with the new 10.x ACL action behavior. (cherry picked from commit 39cf62e6323c82fc4a90f713cbd1ccf5d5731401) --- .../tools_tests/test_convert_remap_actions_to_10x | 99 +++++++++++ tools/remap/convert_remap_actions_to_10x | 196 +++++++++++++++++++++ 2 files changed, 295 insertions(+) diff --git a/tests/tools_tests/test_convert_remap_actions_to_10x b/tests/tools_tests/test_convert_remap_actions_to_10x new file mode 100755 index 0000000000..fe89033de3 --- /dev/null +++ b/tests/tools_tests/test_convert_remap_actions_to_10x @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Verify convert_remap_actions_to_10x behavior. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +tmp_dir=$(mktemp -d -t test_convert_remap_actions_to_10x_XXXXXXXX) +start_remap_config=${tmp_dir}/remap.config +start_remap_copy=${tmp_dir}/remap.orig +expected_10x_config=${tmp_dir}/remap.gold +backup_base=remap.config.convert_actions.bak +script=$(realpath $(dirname $0)/../../tools/remap/convert_remap_actions_to_10x) + +fail() +{ + echo $1 + exit 1 +} + +create_start_remap_config() +{ + cat > ${start_remap_config} <<EOF +# Some comment. +map http://one.com http://backend.one.com @action=allow @method=GET + +# Some comment with @action=allow +map http:://two.com http://backend.two.com +map http://three.com http://backend.three.com @action=deny @method=POST +EOF +} + +reset_test_directory() +{ + rm -rf ${tmp_dir} + mkdir -p ${tmp_dir} + cd ${tmp_dir} + create_start_remap_config + cp ${start_remap_config} ${start_remap_copy} + cp ${start_remap_config} ${expected_10x_config} + sed -i 's/@action=allow/@action=add_allow/g' ${expected_10x_config} + sed -i 's/@action=deny/@action=add_deny/g' ${expected_10x_config} +} + +[ -f ${script} -a -x ${script} ] || fail "${script} is not an executable file." + +# Test a run without parameters. A backup file should be created. +reset_test_directory +${script} remap.config +diff remap.config ${expected_10x_config} || fail "Unexpected content after \"${script} remap.config\"." +[ -f ${backup_base}.0 ] || fail "${backup_base}.0 does not exist." +diff ${backup_base}.0 ${start_remap_copy} || fail "Wrong ${backup_base}.0 content." + +# Re-run on the same file that is now updated. A new backup should be created. +${script} remap.config +diff remap.config ${expected_10x_config} || fail "Unexpected content after second \"${script} remap.config\"." +[ -f ${backup_base}.1 ] || fail "${backup_base}.1 does not exist." +diff ${backup_base}.1 ${expected_10x_config} || fail "Wrong ${backup_base}.1 content." + +# Verify that a third backup is created after re-applying the start content. +cp ${start_remap_copy} remap.config +${script} remap.config +diff remap.config ${expected_10x_config} || fail "Unexpected content after third \"${script} remap.config\"." +[ -f ${backup_base}.2 ] || fail "${backup_base}.2 does not exist." +diff ${backup_base}.2 ${start_remap_copy} || fail "Wrong ${backup_base}.2 content." + +# Test the --no-backup option. +cp ${start_remap_copy} remap.config +${script} remap.config -n +diff remap.config ${expected_10x_config} || fail "Unexpected content after \"${script} remap.config -n\"." +[ -f ${backup_base}.3 ] && fail "${backup_base}.3 should not have been created." + +# Test -o option. +reset_test_directory +${script} remap.config -o myremap.config +diff remap.config ${start_remap_copy} || fail "Unexpected input content after \"${script} remap.config -o myremap.config\"" +diff myremap.config ${expected_10x_config} || fail "Unexpected myremap.config content." + +# Verify sane -o with -n behavior. +reset_test_directory +${script} remap.config -o myremap2.config -n +diff remap.config ${start_remap_copy} || fail "Unexpected input content after \"${script} remap.config -o myremap.config -n\"" +diff myremap2.config ${expected_10x_config} || fail "Unexpected myremap2.config output." + +# Cleanup +rm -rf ${tmp_dir} diff --git a/tools/remap/convert_remap_actions_to_10x b/tools/remap/convert_remap_actions_to_10x new file mode 100755 index 0000000000..e1321529e7 --- /dev/null +++ b/tools/remap/convert_remap_actions_to_10x @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +'''Convert allow and deny @actions to add_allow and add_deny.''' +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# NOTE: there is a corresponding +# tests/tools_tests/test_convert_remap_actions_to_10x test script that is +# associated with this file. As this file is updated, please update that script +# as appropriate and make sure it passes all tests. + +import argparse +import os +import shutil +import sys +import tempfile +from typing import Optional, Tuple + +CLI_HELP_TEXT = '''Convert allow and deny @actions to add_allow and add_deny. + +This script is used to convert a pre-10.x remap.config file to be functionally +equivalent to a 10.x remap.config file. In 10.x, the pre-10.x @action=allow and +@action=deny actions are renamed to @action=add_allow and @action=add_deny, +respectively. This script will convert the former to the latter. For further +details, see the remap.config documentation. +''' + + +def convert_line(input_line: str) -> str: + '''Convert an input line to an output line. + + This function modifies the input line for that of the new 9.2.x format. + + :param input_line: The line to convert. + :return: The converted line. + + :examples: + >>> convert_line('# nothing to convert') + '# nothing to convert' + >>> convert_line('map http://example.com http://backend.example.com') + 'map http://example.com http://backend.example.com' + + >>> convert_line('map http://example.com http://backend.example.com @action=allow @method=GET') + 'map http://example.com http://backend.example.com @action=add_allow @method=GET' + >>> convert_line('map http://example.com http://backend.example.com @action=deny @method=PUT') + 'map http://example.com http://backend.example.com @action=add_deny @method=PUT' + + # Verify that add_allow and add_deny are not converted. + >>> convert_line('map http://example.com http://backend.example.com @action=add_deny @method=PUT') + 'map http://example.com http://backend.example.com @action=add_deny @method=PUT' + >>> convert_line('map http://example.com http://backend.example.com @action=add_allow @method=GET') + 'map http://example.com http://backend.example.com @action=add_allow @method=GET' + + # Comments should be converted too. + >>> convert_line('# Using @action=allow is nice.') + '# Using @action=add_allow is nice.' + >>> convert_line('# map http://example.com http://backend.example.com @action=allow @method=GET') + '# map http://example.com http://backend.example.com @action=add_allow @method=GET' + ''' + output_line = input_line.replace('@action=allow', '@action=add_allow') + output_line = output_line.replace('@action=deny', '@action=add_deny') + return output_line + + +def get_max_rotation_index(files: list[str], rotation_base: str) -> int: + '''Find the largest <num> in <rotation_base>.convert_actions.bak.<num>. + + :param files: The list of files to search for backup files for. + :rotation base: The base backup prefix preceding the index. + :return: The index for the highest rotation index. -1 if no backup files are found. + + :examples: + >>> get_max_rotation_index(['base.0', 'base.1', 'base.2'], 'base.') + 2 + >>> get_max_rotation_index([], 'base.') + -1 + >>> get_max_rotation_index(['some', 'other', 'files'], 'base.') + -1 + >>> get_max_rotation_index(['some', 'other', 'files', 'base.0', 'base.1'], 'base.') + 1 + >>> get_max_rotation_index(['remap.config', 'remap.config.convert_actions.bak.0', 'remap.config.convert_actions.bak.1'], 'remap.config.convert_actions.bak.') + 1 + ''' + max_index = -1 + for file in files: + if not file.startswith(rotation_base): + continue + suffix = file.split('.')[-1] + if not suffix.isnumeric(): + continue + max_index = max(max_index, int(suffix)) + return max_index + + +def copy_input_file(input: str, /, no_backup: bool) -> str: + '''Create a copy of the input file. + + :param input: The input file path from which to create a copy. + + :param no_backup: Do not create a backup file from input. Rather simply + create a temporary file that will be later removed. Otherwise, create a + backup file of the format <input>.convert_actions.bak.<num>, where <num> is + one greater than the greatest value in the directory where @a input exists. + + :return: The path to the copied file. + ''' + if no_backup: + copy_path = tempfile.NamedTemporaryFile(delete=False).name + else: + input_dir = os.path.dirname(input) + input_dir = '.' if input_dir == '' else input_dir + rotation_base = f'{os.path.basename(input)}.convert_actions.bak' + max_index = get_max_rotation_index(os.listdir(input_dir), rotation_base) + copy_path = os.path.join(input_dir, f'{rotation_base}.{max_index + 1}') + shutil.copyfile(input, copy_path) + return copy_path + + +def prepare_files(input: str, output: Optional[str], /, no_backup: bool) -> Tuple[str, str, bool]: + '''Prepare the input and output files. + + :param input: The input file path. + :param output: The output file path. + :param no_backup: Do not create a backup of the input file when <input> is modified. + + :return: A tuple containing the input and output filenames, in that order, and + a boolean telling the caller whether to delete the input file. + ''' + if not os.path.exists(input): + raise FileNotFoundError(f'Input file does not exist: {input}') + + if input == output or output is None: + input_path = copy_input_file(input, no_backup=no_backup) + output_path = input + delete_input_file = no_backup + else: + input_path = input + output_path = output + delete_input_file = False + return input_path, output_path, delete_input_file + + +def parse_args() -> argparse.Namespace: + '''Parse command line arguments. + + :return: The parsed arguments. + ''' + parser = argparse.ArgumentParser(description=CLI_HELP_TEXT) + parser.add_argument('input', help='The input remap.config file to modify.') + parser.add_argument( + '-o', + '--output', + help='The output remap.config file. By default, output is written to <input> and a backup file is generated.') + parser.add_argument('-n', '--no-backup', action='store_true', help='Do not create a backup file.') + return parser.parse_args() + + +def main() -> int: + '''Convert the input remap.config file. + + :return: The process exit code. + ''' + + args = parse_args() + input_path, output_path, should_delete_input = prepare_files(args.input, args.output, no_backup=args.no_backup) + + try: + with open(input_path, 'r') as fd_in, open(output_path, 'w') as fd_out: + for input_line in fd_in: + output_line = convert_line(input_line) + fd_out.write(output_line) + finally: + if should_delete_input and os.path.exists(input_path): + os.remove(input_path) + + return 0 + + +if __name__ == '__main__': + import doctest + doctest.testmod() + sys.exit(main())
