This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 39cf62e632 remap_action_to_add: helper script for new ACL actions 
(#11583)
39cf62e632 is described below

commit 39cf62e6323c82fc4a90f713cbd1ccf5d5731401
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.
---
 .../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())

Reply via email to