From 5ae553b078bff5ab48b63e78a9d30ad166f0543c Mon Sep 17 00:00:00 2001 From: Tim Byrne Date: Mon, 17 Jan 2022 13:46:31 -0600 Subject: [PATCH] Add support for distro_family (#213) Obtained from /etc/os-release: ID_LIKE. Alternate attributes f & distro_family. --- test/conftest.py | 11 ++++ test/test_alt.py | 5 +- test/test_unit_query_distro_family.py | 26 ++++++++++ test/test_unit_score_file.py | 10 ++-- test/test_unit_set_local_alt_values.py | 9 ++-- test/test_unit_template_default.py | 40 ++++++++++----- test/test_unit_template_esh.py | 40 ++++++++++----- test/test_unit_template_j2.py | 40 ++++++++++----- yadm | 70 ++++++++++++++++++-------- 9 files changed, 187 insertions(+), 64 deletions(-) create mode 100644 test/test_unit_query_distro_family.py diff --git a/test/conftest.py b/test/conftest.py index ea7eee6..95e2fca 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -68,6 +68,17 @@ def tst_distro(runner): return distro +@pytest.fixture(scope='session') +def tst_distro_family(runner): + """Test session's distro_family""" + family = '' + with contextlib.suppress(Exception): + run = runner(command=[ + 'grep', '-oP', r'ID_LIKE=\K.+', '/etc/os-release'], report=False) + family = run.out.strip() + return family + + @pytest.fixture(scope='session') def tst_sys(): """Test session's uname value""" diff --git a/test/test_alt.py b/test/test_alt.py index 45c9055..e0f40f1 100644 --- a/test/test_alt.py +++ b/test/test_alt.py @@ -85,13 +85,15 @@ def test_relative_link(runner, paths, yadm_alt): '##a.$tst_arch', '##arch.$tst_arch', '##architecture.$tst_arch', '##o.$tst_sys', '##os.$tst_sys', '##d.$tst_distro', '##distro.$tst_distro', + '##f.$tst_distro_family', '##distro_family.$tst_distro_family', '##c.$tst_class', '##class.$tst_class', '##h.$tst_host', '##hostname.$tst_host', '##u.$tst_user', '##user.$tst_user', ]) def test_alt_conditions( runner, paths, - tst_arch, tst_sys, tst_distro, tst_host, tst_user, suffix): + tst_arch, tst_sys, tst_distro, tst_distro_family, tst_host, tst_user, + suffix): """Test conditions supported by yadm alt""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) @@ -103,6 +105,7 @@ def test_alt_conditions( tst_arch=tst_arch, tst_sys=tst_sys, tst_distro=tst_distro, + tst_distro_family=tst_distro_family, tst_class=tst_class, tst_host=tst_host, tst_user=tst_user, diff --git a/test/test_unit_query_distro_family.py b/test/test_unit_query_distro_family.py new file mode 100644 index 0000000..bf68319 --- /dev/null +++ b/test/test_unit_query_distro_family.py @@ -0,0 +1,26 @@ +"""Unit tests: query_distro_family""" +import pytest + + +@pytest.mark.parametrize( + 'condition', ['os-release', 'os-release-quotes', 'missing']) +def test_query_distro_family(runner, yadm, tmp_path, condition): + """Match ID_LIKE when present""" + test_family = 'testfamily' + os_release = tmp_path.joinpath('os-release') + if 'os-release' in condition: + quotes = '"' if 'quotes' in condition else '' + os_release.write_text( + f"testing\nID_LIKE={quotes}{test_family}{quotes}\nfamily") + script = f""" + YADM_TEST=1 source {yadm} + OS_RELEASE="{os_release}" + query_distro_family + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + if 'os-release' in condition: + assert run.out.rstrip() == test_family + else: + assert run.out.rstrip() == '' diff --git a/test/test_unit_score_file.py b/test/test_unit_score_file.py index f7b821a..c89516b 100644 --- a/test/test_unit_score_file.py +++ b/test/test_unit_score_file.py @@ -18,17 +18,21 @@ CONDITION = { 'labels': ['d', 'distro'], 'modifier': 4, }, + 'distro_family': { + 'labels': ['f', 'distro_family'], + 'modifier': 8, + }, 'class': { 'labels': ['c', 'class'], - 'modifier': 8, + 'modifier': 16, }, 'hostname': { 'labels': ['h', 'hostname'], - 'modifier': 16, + 'modifier': 32, }, 'user': { 'labels': ['u', 'user'], - 'modifier': 32, + 'modifier': 64, }, } TEMPLATE_LABELS = ['t', 'template', 'yadm'] diff --git a/test/test_unit_set_local_alt_values.py b/test/test_unit_set_local_alt_values.py index 947a31c..b177c2b 100644 --- a/test/test_unit_set_local_alt_values.py +++ b/test/test_unit_set_local_alt_values.py @@ -70,17 +70,20 @@ def test_set_local_alt_values( assert f"user='{tst_user}'" in run.out -def test_distro(runner, yadm): - """Assert that local_distro is set""" +def test_distro_and_family(runner, yadm): + """Assert that local_distro/local_distro_family are set""" script = f""" YADM_TEST=1 source {yadm} function config() {{ echo "$1"; }} function query_distro() {{ echo "testdistro"; }} + function query_distro_family() {{ echo "testfamily"; }} set_local_alt_values echo "distro='$local_distro'" + echo "distro_family='$local_distro_family'" """ run = runner(command=['bash'], inp=script) assert run.success assert run.err == '' - assert run.out.strip() == "distro='testdistro'" + assert "distro='testdistro'" in run.out + assert "distro_family='testfamily'" in run.out diff --git a/test/test_unit_template_default.py b/test/test_unit_template_default.py index f503a39..36098e2 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -10,14 +10,16 @@ LOCAL_SYSTEM = "default_Test+@-!^System" LOCAL_HOST = "default_Test+@-!^Host" LOCAL_USER = "default_Test+@-!^User" LOCAL_DISTRO = "default_Test+@-!^Distro" +LOCAL_DISTRO_FAMILY = "default_Test+@-!^Family" TEMPLATE = f''' start of template -default class = >{{{{yadm.class}}}}< -default arch = >{{{{yadm.arch}}}}< -default os = >{{{{yadm.os}}}}< -default host = >{{{{yadm.hostname}}}}< -default user = >{{{{yadm.user}}}}< -default distro = >{{{{yadm.distro}}}}< +default class = >{{{{yadm.class}}}}< +default arch = >{{{{yadm.arch}}}}< +default os = >{{{{yadm.os}}}}< +default host = >{{{{yadm.hostname}}}}< +default user = >{{{{yadm.user}}}}< +default distro = >{{{{yadm.distro}}}}< +default distro_family = >{{{{yadm.distro_family}}}}< {{% if yadm.class == "else1" %}} wrong else 1 {{% else %}} @@ -80,16 +82,27 @@ Included section for distro = {{{{yadm.distro}}}} ({{{{yadm.distro}}}} again) {{% if yadm.distro == "wrongdistro2" %}} wrong distro 2 {{% endif %}} +{{% if yadm.distro_family == "wrongfamily1" %}} +wrong family 1 +{{% endif %}} +{{% if yadm.distro_family == "{LOCAL_DISTRO_FAMILY}" %}} +Included section for distro_family = \ +{{{{yadm.distro_family}}}} ({{{{yadm.distro_family}}}} again) +{{% endif %}} +{{% if yadm.distro_family == "wrongfamily2" %}} +wrong family 2 +{{% endif %}} end of template ''' EXPECTED = f''' start of template -default class = >{LOCAL_CLASS}< -default arch = >{LOCAL_ARCH}< -default os = >{LOCAL_SYSTEM}< -default host = >{LOCAL_HOST}< -default user = >{LOCAL_USER}< -default distro = >{LOCAL_DISTRO}< +default class = >{LOCAL_CLASS}< +default arch = >{LOCAL_ARCH}< +default os = >{LOCAL_SYSTEM}< +default host = >{LOCAL_HOST}< +default user = >{LOCAL_USER}< +default distro = >{LOCAL_DISTRO}< +default distro_family = >{LOCAL_DISTRO_FAMILY}< Included section from else Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) Multiple lines @@ -98,6 +111,8 @@ Included section for os = {LOCAL_SYSTEM} ({LOCAL_SYSTEM} repeated) Included section for host = {LOCAL_HOST} ({LOCAL_HOST} again) Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated) Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) +Included section for distro_family = \ +{LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) end of template ''' @@ -155,6 +170,7 @@ def test_template_default(runner, yadm, tmpdir): local_host="{LOCAL_HOST}" local_user="{LOCAL_USER}" local_distro="{LOCAL_DISTRO}" + local_distro_family="{LOCAL_DISTRO_FAMILY}" template_default "{input_file}" "{output_file}" """ run = runner(command=['bash'], inp=script) diff --git a/test/test_unit_template_esh.py b/test/test_unit_template_esh.py index ee3888a..d4adc70 100644 --- a/test/test_unit_template_esh.py +++ b/test/test_unit_template_esh.py @@ -9,14 +9,16 @@ LOCAL_SYSTEM = "esh_Test+@-!^System" LOCAL_HOST = "esh_Test+@-!^Host" LOCAL_USER = "esh_Test+@-!^User" LOCAL_DISTRO = "esh_Test+@-!^Distro" +LOCAL_DISTRO_FAMILY = "esh_Test+@-!^Family" TEMPLATE = f''' start of template -esh class = ><%=$YADM_CLASS%>< -esh arch = ><%=$YADM_ARCH%>< -esh os = ><%=$YADM_OS%>< -esh host = ><%=$YADM_HOSTNAME%>< -esh user = ><%=$YADM_USER%>< -esh distro = ><%=$YADM_DISTRO%>< +esh class = ><%=$YADM_CLASS%>< +esh arch = ><%=$YADM_ARCH%>< +esh os = ><%=$YADM_OS%>< +esh host = ><%=$YADM_HOSTNAME%>< +esh user = ><%=$YADM_USER%>< +esh distro = ><%=$YADM_DISTRO%>< +esh distro_family = ><%=$YADM_DISTRO_FAMILY%>< <% if [ "$YADM_CLASS" = "wrongclass1" ]; then -%> wrong class 1 <% fi -%> @@ -71,22 +73,35 @@ Included section for distro = <%=$YADM_DISTRO%> (<%=$YADM_DISTRO%> again) <% if [ "$YADM_DISTRO" = "wrongdistro2" ]; then -%> wrong distro 2 <% fi -%> +<% if [ "$YADM_DISTRO_FAMILY" = "wrongfamily1" ]; then -%> +wrong family 1 +<% fi -%> +<% if [ "$YADM_DISTRO_FAMILY" = "{LOCAL_DISTRO_FAMILY}" ]; then -%> +Included section for distro_family = \ +<%=$YADM_DISTRO_FAMILY%> (<%=$YADM_DISTRO_FAMILY%> again) +<% fi -%> +<% if [ "$YADM_DISTRO" = "wrongfamily2" ]; then -%> +wrong family 2 +<% fi -%> end of template ''' EXPECTED = f''' start of template -esh class = >{LOCAL_CLASS}< -esh arch = >{LOCAL_ARCH}< -esh os = >{LOCAL_SYSTEM}< -esh host = >{LOCAL_HOST}< -esh user = >{LOCAL_USER}< -esh distro = >{LOCAL_DISTRO}< +esh class = >{LOCAL_CLASS}< +esh arch = >{LOCAL_ARCH}< +esh os = >{LOCAL_SYSTEM}< +esh host = >{LOCAL_HOST}< +esh user = >{LOCAL_USER}< +esh distro = >{LOCAL_DISTRO}< +esh distro_family = >{LOCAL_DISTRO_FAMILY}< Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) Included section for arch = {LOCAL_ARCH} ({LOCAL_ARCH} repeated) Included section for os = {LOCAL_SYSTEM} ({LOCAL_SYSTEM} repeated) Included section for host = {LOCAL_HOST} ({LOCAL_HOST} again) Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated) Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) +Included section for distro_family = \ +{LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) end of template ''' @@ -113,6 +128,7 @@ def test_template_esh(runner, yadm, tmpdir): local_host="{LOCAL_HOST}" local_user="{LOCAL_USER}" local_distro="{LOCAL_DISTRO}" + local_distro_family="{LOCAL_DISTRO_FAMILY}" template_esh "{input_file}" "{output_file}" """ run = runner(command=['bash'], inp=script) diff --git a/test/test_unit_template_j2.py b/test/test_unit_template_j2.py index 6e4c5b0..7429f8d 100644 --- a/test/test_unit_template_j2.py +++ b/test/test_unit_template_j2.py @@ -10,14 +10,16 @@ LOCAL_SYSTEM = "j2_Test+@-!^System" LOCAL_HOST = "j2_Test+@-!^Host" LOCAL_USER = "j2_Test+@-!^User" LOCAL_DISTRO = "j2_Test+@-!^Distro" +LOCAL_DISTRO_FAMILY = "j2_Test+@-!^Family" TEMPLATE = f''' start of template -j2 class = >{{{{YADM_CLASS}}}}< -j2 arch = >{{{{YADM_ARCH}}}}< -j2 os = >{{{{YADM_OS}}}}< -j2 host = >{{{{YADM_HOSTNAME}}}}< -j2 user = >{{{{YADM_USER}}}}< -j2 distro = >{{{{YADM_DISTRO}}}}< +j2 class = >{{{{YADM_CLASS}}}}< +j2 arch = >{{{{YADM_ARCH}}}}< +j2 os = >{{{{YADM_OS}}}}< +j2 host = >{{{{YADM_HOSTNAME}}}}< +j2 user = >{{{{YADM_USER}}}}< +j2 distro = >{{{{YADM_DISTRO}}}}< +j2 distro_family = >{{{{YADM_DISTRO_FAMILY}}}}< {{%- if YADM_CLASS == "wrongclass1" %}} wrong class 1 {{%- endif %}} @@ -72,22 +74,35 @@ Included section for distro = {{{{YADM_DISTRO}}}} ({{{{YADM_DISTRO}}}} again) {{%- if YADM_DISTRO == "wrongdistro2" %}} wrong distro 2 {{%- endif %}} +{{%- if YADM_DISTRO_FAMILY == "wrongfamily1" %}} +wrong family 1 +{{%- endif %}} +{{%- if YADM_DISTRO_FAMILY == "{LOCAL_DISTRO_FAMILY}" %}} +Included section for distro_family = \ +{{{{YADM_DISTRO_FAMILY}}}} ({{{{YADM_DISTRO_FAMILY}}}} again) +{{%- endif %}} +{{%- if YADM_DISTRO_FAMILY == "wrongfamily2" %}} +wrong family 2 +{{%- endif %}} end of template ''' EXPECTED = f''' start of template -j2 class = >{LOCAL_CLASS}< -j2 arch = >{LOCAL_ARCH}< -j2 os = >{LOCAL_SYSTEM}< -j2 host = >{LOCAL_HOST}< -j2 user = >{LOCAL_USER}< -j2 distro = >{LOCAL_DISTRO}< +j2 class = >{LOCAL_CLASS}< +j2 arch = >{LOCAL_ARCH}< +j2 os = >{LOCAL_SYSTEM}< +j2 host = >{LOCAL_HOST}< +j2 user = >{LOCAL_USER}< +j2 distro = >{LOCAL_DISTRO}< +j2 distro_family = >{LOCAL_DISTRO_FAMILY}< Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) Included section for arch = {LOCAL_ARCH} ({LOCAL_ARCH} repeated) Included section for os = {LOCAL_SYSTEM} ({LOCAL_SYSTEM} repeated) Included section for host = {LOCAL_HOST} ({LOCAL_HOST} again) Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated) Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) +Included section for distro_family = \ +{LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) end of template ''' @@ -115,6 +130,7 @@ def test_template_j2(runner, yadm, tmpdir, processor): local_host="{LOCAL_HOST}" local_user="{LOCAL_USER}" local_distro="{LOCAL_DISTRO}" + local_distro_family="{LOCAL_DISTRO_FAMILY}" template_{processor} "{input_file}" "{output_file}" """ run = runner(command=['bash'], inp=script) diff --git a/yadm b/yadm index 73e6044..be020d7 100755 --- a/yadm +++ b/yadm @@ -210,23 +210,30 @@ function score_file() { score=0 return fi + elif [[ "$label" =~ ^(f|distro_family)$ ]]; then + if [ "$value" = "$local_distro_family" ]; then + score=$((score + 8)) + else + score=0 + return + fi elif [[ "$label" =~ ^(c|class)$ ]]; then if [ "$value" = "$local_class" ]; then - score=$((score + 8)) + score=$((score + 16)) else score=0 return fi elif [[ "$label" =~ ^(h|hostname)$ ]]; then if [ "$value" = "$local_host" ]; then - score=$((score + 16)) + score=$((score + 32)) else score=0 return fi elif [[ "$label" =~ ^(u|user)$ ]]; then if [ "$value" = "$local_user" ]; then - score=$((score + 32)) + score=$((score + 64)) else score=0 return @@ -363,24 +370,25 @@ function template_default() { read -r -d '' awk_pgm << "EOF" # built-in default template processor BEGIN { - blank = "[ ]" - c["class"] = class - c["arch"] = arch - c["os"] = os - c["hostname"] = host - c["user"] = user - c["distro"] = distro - c["source"] = source - ifs = "^{%" blank "*if" - els = "^{%" blank "*else" blank "*%}$" - end = "^{%" blank "*endif" blank "*%}$" - skp = "^{%" blank "*(if|else|endif)" - vld = conditions() - inc_start = "^{%" blank "*include" blank "+\"?" - inc_end = "\"?" blank "*%}$" - inc = inc_start ".+" inc_end - prt = 1 - err = 0 + blank = "[ ]" + c["class"] = class + c["arch"] = arch + c["os"] = os + c["hostname"] = host + c["user"] = user + c["distro"] = distro + c["distro_family"] = distro_family + c["source"] = source + ifs = "^{%" blank "*if" + els = "^{%" blank "*else" blank "*%}$" + end = "^{%" blank "*endif" blank "*%}$" + skp = "^{%" blank "*(if|else|endif)" + vld = conditions() + inc_start = "^{%" blank "*include" blank "+\"?" + inc_end = "\"?" blank "*%}$" + inc = inc_start ".+" inc_end + prt = 1 + err = 0 } END { exit err } { replace_vars() } # variable replacements @@ -437,6 +445,7 @@ EOF -v host="$local_host" \ -v user="$local_user" \ -v distro="$local_distro" \ + -v distro_family="$local_distro_family" \ -v source="$input" \ -v source_dir="$(dirname "$input")" \ "$awk_pgm" \ @@ -456,6 +465,7 @@ function template_j2cli() { YADM_HOSTNAME="$local_host" \ YADM_USER="$local_user" \ YADM_DISTRO="$local_distro" \ + YADM_DISTRO_FAMILY="$local_distro_family" \ YADM_SOURCE="$input" \ "$J2CLI_PROGRAM" "$input" -o "$temp_file" @@ -473,6 +483,7 @@ function template_envtpl() { YADM_HOSTNAME="$local_host" \ YADM_USER="$local_user" \ YADM_DISTRO="$local_distro" \ + YADM_DISTRO_FAMILY="$local_distro_family" \ YADM_SOURCE="$input" \ "$ENVTPL_PROGRAM" --keep-template "$input" -o "$temp_file" @@ -491,6 +502,7 @@ function template_esh() { YADM_HOSTNAME="$local_host" \ YADM_USER="$local_user" \ YADM_DISTRO="$local_distro" \ + YADM_DISTRO_FAMILY="$local_distro_family" \ YADM_SOURCE="$input" move_file "$input" "$output" "$temp_file" @@ -526,6 +538,7 @@ function alt() { local local_host local local_user local local_distro + local local_distro_family set_local_alt_values # only be noisy if the "alt" command was run directly @@ -644,6 +657,7 @@ function set_local_alt_values() { fi local_distro="$(query_distro)" + local_distro_family="$(query_distro_family)" } @@ -1486,6 +1500,20 @@ function query_distro() { echo "$distro" } +function query_distro_family() { + family="" + if [ -f "$OS_RELEASE" ]; then + while IFS='' read -r line || [ -n "$line" ]; do + if [[ "$line" = ID_LIKE=* ]]; then + family="${line#ID_LIKE=}" + family="${family//\"}" + break + fi + done < "$OS_RELEASE" + fi + echo "$family" +} + function process_global_args() { # global arguments are removed before the main processing is done