diff --git a/test/test_unit_copy_perms.py b/test/test_unit_copy_perms.py new file mode 100644 index 0000000..3c79768 --- /dev/null +++ b/test/test_unit_copy_perms.py @@ -0,0 +1,53 @@ +"""Unit tests: copy_perms""" +import os +import pytest + +OCTAL = '7654' +NON_OCTAL = '9876' + + +@pytest.mark.parametrize( + 'stat_broken', [True, False], ids=['normal', 'stat broken']) +def test_copy_perms(runner, yadm, tmpdir, stat_broken): + """Test function copy_perms""" + src_mode = 0o754 + dst_mode = 0o644 + source = tmpdir.join('source') + source.write('test', ensure=True) + source.chmod(src_mode) + + dest = tmpdir.join('dest') + dest.write('test', ensure=True) + dest.chmod(dst_mode) + + override_stat = '' + if stat_broken: + override_stat = 'function stat() { echo broken; }' + script = f""" + YADM_TEST=1 source {yadm} + {override_stat} + copy_perms "{source}" "{dest}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + assert run.out == '' + expected = dst_mode if stat_broken else src_mode + assert oct(os.stat(dest).st_mode)[-3:] == oct(expected)[-3:] + + +@pytest.mark.parametrize( + 'stat_output', [OCTAL, NON_OCTAL], ids=['octal', 'non-octal']) +def test_get_mode(runner, yadm, stat_output): + """Test function get_mode""" + script = f""" + YADM_TEST=1 source {yadm} + function stat() {{ echo {stat_output}; }} + mode=$(get_mode abc) + echo "MODE:$mode" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + expected = OCTAL if stat_output == OCTAL else "" + assert f'MODE:{expected}\n' in run.out diff --git a/test/test_unit_template_default.py b/test/test_unit_template_default.py index 42464b8..cbc42d5 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -1,4 +1,7 @@ """Unit tests: template_default""" +import os + +FILE_MODE = 0o754 # these values are also testing the handling of bizarre characters LOCAL_CLASS = "default_Test+@-!^Class" @@ -91,6 +94,7 @@ def test_template_default(runner, yadm, tmpdir): input_file = tmpdir.join('input') input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -107,6 +111,7 @@ def test_template_default(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read() == EXPECTED + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode def test_source(runner, yadm, tmpdir): @@ -114,6 +119,7 @@ def test_source(runner, yadm, tmpdir): input_file = tmpdir.join('input') input_file.write('{{yadm.source}}', ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -125,3 +131,4 @@ def test_source(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read().strip() == str(input_file) + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/test/test_unit_template_esh.py b/test/test_unit_template_esh.py index c290b5f..e975152 100644 --- a/test/test_unit_template_esh.py +++ b/test/test_unit_template_esh.py @@ -1,4 +1,7 @@ """Unit tests: template_esh""" +import os + +FILE_MODE = 0o754 LOCAL_CLASS = "esh_Test+@-!^Class" LOCAL_SYSTEM = "esh_Test+@-!^System" @@ -80,6 +83,7 @@ def test_template_esh(runner, yadm, tmpdir): input_file = tmpdir.join('input') input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -95,6 +99,7 @@ def test_template_esh(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read().strip() == str(EXPECTED).strip() + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode def test_source(runner, yadm, tmpdir): @@ -102,6 +107,7 @@ def test_source(runner, yadm, tmpdir): input_file = tmpdir.join('input') input_file.write('<%= $YADM_SOURCE %>', ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -112,3 +118,4 @@ def test_source(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read().strip() == str(input_file) + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/test/test_unit_template_j2.py b/test/test_unit_template_j2.py index 85c6822..f81f4c6 100644 --- a/test/test_unit_template_j2.py +++ b/test/test_unit_template_j2.py @@ -1,6 +1,9 @@ """Unit tests: template_j2cli & template_envtpl""" +import os import pytest +FILE_MODE = 0o754 + LOCAL_CLASS = "j2_Test+@-!^Class" LOCAL_SYSTEM = "j2_Test+@-!^System" LOCAL_HOST = "j2_Test+@-!^Host" @@ -82,6 +85,7 @@ def test_template_j2(runner, yadm, tmpdir, processor): input_file = tmpdir.join('input') input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -97,6 +101,7 @@ def test_template_j2(runner, yadm, tmpdir, processor): assert run.success assert run.err == '' assert output_file.read() == EXPECTED + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode @pytest.mark.parametrize('processor', ('j2cli', 'envtpl')) @@ -105,6 +110,7 @@ def test_source(runner, yadm, tmpdir, processor): input_file = tmpdir.join('input') input_file.write('{{YADM_SOURCE}}', ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -115,3 +121,4 @@ def test_source(runner, yadm, tmpdir, processor): assert run.success assert run.err == '' assert output_file.read().strip() == str(input_file) + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/yadm b/yadm index 4b43e4f..9105fa4 100755 --- a/yadm +++ b/yadm @@ -394,7 +394,11 @@ EOF -v source="$input" \ "$awk_pgm" \ "$input" > "$temp_file" - [ -f "$temp_file" ] && mv -f "$temp_file" "$output" + + if [ -f "$temp_file" ] ; then + copy_perms "$input" "$temp_file" + mv -f "$temp_file" "$output" + fi } function template_j2cli() { @@ -409,7 +413,11 @@ function template_j2cli() { YADM_DISTRO="$local_distro" \ YADM_SOURCE="$input" \ "$J2CLI_PROGRAM" "$input" -o "$temp_file" - [ -f "$temp_file" ] && mv -f "$temp_file" "$output" + + if [ -f "$temp_file" ] ; then + copy_perms "$input" "$temp_file" + mv -f "$temp_file" "$output" + fi } function template_envtpl() { @@ -424,7 +432,11 @@ function template_envtpl() { YADM_DISTRO="$local_distro" \ YADM_SOURCE="$input" \ "$ENVTPL_PROGRAM" --keep-template "$input" -o "$temp_file" - [ -f "$temp_file" ] && mv -f "$temp_file" "$output" + + if [ -f "$temp_file" ] ; then + copy_perms "$input" "$temp_file" + mv -f "$temp_file" "$output" + fi } function template_esh() { @@ -440,7 +452,10 @@ function template_esh() { YADM_DISTRO="$local_distro" \ YADM_SOURCE="$input" - [ -f "$temp_file" ] && mv -f "$temp_file" "$output" + if [ -f "$temp_file" ] ; then + copy_perms "$input" "$temp_file" + mv -f "$temp_file" "$output" + fi } # ****** yadm Commands ****** @@ -1908,6 +1923,33 @@ function join_string { printf "%s" "${*:2}" } +function get_mode { + local filename="$1" + local mode + + # most *nixes + mode=$(stat -c '%a' "$filename" 2>/dev/null) + if [ -z "$mode" ] ; then + # BSD-style + mode=$(stat -f '%A' "$filename" 2>/dev/null) + fi + + # only accept results if they are octal + if [[ ! $mode =~ ^[0-7]+$ ]] ; then + mode="" + fi + + echo "$mode" +} + +function copy_perms { + local source="$1" + local dest="$2" + mode=$(get_mode "$source") + [ -n "$mode" ] && chmod "$mode" "$dest" + return 0 +} + # ****** Prerequisites Functions ****** function require_archive() {