From 2716ca515145d0246790b1b43e423106f5e0ea6c Mon Sep 17 00:00:00 2001 From: Nicholas de Jong Date: Tue, 16 Jul 2024 21:21:23 +1000 Subject: [PATCH] release 0.5.1 New definition attributes Support for Keepass and Ansible-vault source formats Support for auto-generated ANSIBLE_PASSWORD_FILE New input handling via STDIN and Python-getpass Better error handling Improved documentation Lots and lots of tests Migrate project to more recent version of slap-cli for appdev management Update previous changelog files Update build tests to include keepassxc --- .changelog/0.3.1.toml | 7 + .changelog/0.3.1.yml | 6 - .changelog/0.3.3.toml | 7 + .changelog/0.3.3.yml | 6 - .changelog/0.3.4.toml | 7 + .changelog/0.3.4.yml | 7 - .changelog/0.3.5.toml | 7 + .changelog/0.3.5.yml | 6 - .changelog/0.3.6.toml | 7 + .changelog/0.3.6.yml | 6 - .changelog/0.3.7.toml | 7 + .changelog/0.3.7.yml | 5 - .changelog/0.3.8.toml | 7 + .changelog/0.3.8.yml | 7 - .changelog/0.4.0.toml | 7 + .changelog/0.4.0.yml | 5 - .changelog/0.5.1.toml | 7 + .flake8 | 23 ++ .github/workflows/build-tests.yml | 60 ++--- .gitignore | 138 ++---------- .pylintrc | 18 -- .readthedocs.yml | 27 ++- .safety-policy.yml | 13 ++ CHANGELOG.md | 21 -- MANIFEST.in | 5 - README.md | 109 ++++++--- docs/.readthedocs-custom-steps.yml | 3 - docs/.readthedocs-requirements.txt | 3 - docs/build.novella | 17 ++ docs/configuration-parsers.md | 31 --- docs/configuration-selectors.md | 28 --- docs/configuration-sources.md | 57 ----- docs/configuration.md | 54 ----- docs/content/assets/icons8-keepassxc.svg | 11 + docs/content/assets/terminal-128x128.png | Bin 0 -> 1408 bytes .../ansible-vault-password-file.md | 44 ++++ .../ansible-vault-password.md | 67 ++++++ docs/content/definition-attributes/exec.md | 70 ++++++ .../definition-attributes/keepass-password.md | 46 ++++ docs/content/definition-attributes/name.md | 38 ++++ .../content/definition-attributes/override.md | 22 ++ .../content/definition-attributes/overview.md | 59 +++++ docs/content/definition-attributes/parser.md | 53 +++++ .../content/definition-attributes/selector.md | 47 ++++ docs/content/definition-attributes/source.md | 80 +++++++ docs/content/definition-attributes/value.md | 29 +++ docs/content/development.md | 34 +++ docs/content/examples/ansible-project.md | 31 +++ docs/content/examples/aws-credentials.md | 24 ++ docs/content/examples/debugging.md | 22 ++ .../content/examples/terraform-aws-project.md | 42 ++++ docs/content/index.md | 8 + docs/content/license.md | 11 + docs/content/project.md | 21 ++ docs/development.md | 43 ---- docs/examples-debug.md | 18 -- docs/examples-larger.md | 95 -------- docs/examples-simple.md | 200 ---------------- docs/mkdocs.yml | 30 +++ docs/project.md | 36 --- docs/pydoc-markdown.yml | 118 ---------- package.yml | 45 ---- pyproject.toml | 101 +++++++++ setup.py | 50 ---- src/bin/env-alias | 15 -- src/bin/env-alias-generator | 15 -- src/env_alias/__init__.py | 22 +- src/env_alias/cli/entrypoints.py | 72 ------ src/env_alias/env_alias.py | 15 -- src/env_alias/env_alias_generator.py | 75 ------ src/env_alias/exceptions.py | 20 ++ src/env_alias/exceptions/__init__.py | 2 - src/env_alias/{cli => lib}/__init__.py | 0 src/env_alias/lib/definitions.py | 61 +++++ src/env_alias/lib/generator.py | 146 ++++++++++++ src/env_alias/lib/logger.py | 110 +++++++++ src/env_alias/lib/selector.py | 84 +++++++ src/env_alias/lib/source.py | 213 ++++++++++++++++++ src/env_alias/main.py | 71 ++++++ src/env_alias/models/envalias_definition.py | 79 +++++++ src/env_alias/models/sourced_content.py | 13 ++ .../env_alias/py.typed | 0 src/env_alias/utils/__init__.py | 0 src/env_alias/utils/config.py | 83 ------- src/env_alias/utils/content.py | 73 ------ src/env_alias/utils/logger.py | 86 ------- src/env_alias/utils/selector.py | 81 ------- src/tests/samples/local.ini | 3 - src/tests/samples/local.json | 8 - src/tests/samples/local.txt | 4 - src/tests/samples/local.yml | 6 - src/tests/test_exec.py | 64 ------ src/tests/test_local_ini.py | 70 ------ src/tests/test_local_text.py | 69 ------ src/tests/test_local_yaml.py | 73 ------ {src/tests => tests}/__init__.py | 6 +- ...valias-ansible-vault-datafile-password.txt | 9 + .../envalias-ansible-vault-datafile.vault | 10 + .../envalias-keepass-database-password.txt | 9 + tests/data/envalias-keepass-database.kdbx | Bin 0 -> 3077 bytes tests/manual/ansible_vault_01.yml | 17 ++ .../manual/ansible_vault_password_file_01.yml | 11 + .../configs => tests/manual}/direct_01.yml | 0 .../configs => tests/manual}/direct_02.yml | 0 .../configs => tests/manual}/direct_03.yml | 0 .../configs => tests/manual}/exec_01.yml | 0 .../configs => tests/manual}/exec_02.yml | 0 .../configs => tests/manual}/exec_03.yml | 0 .../configs => tests/manual}/exec_04.yml | 2 +- tests/manual/exec_bad_01.yml | 4 + tests/manual/inputs_01.yml | 14 ++ tests/manual/keepass_01.yml | 17 ++ .../configs => tests/manual}/local_ini_01.yml | 0 .../configs => tests/manual}/local_ini_02.yml | 0 .../configs => tests/manual}/local_ini_03.yml | 0 .../manual}/local_json_01.yml | 0 .../manual}/local_json_02.yml | 0 .../manual}/local_text_01.yml | 0 .../manual}/local_text_02.yml | 0 .../manual}/local_yaml_01.yml | 0 .../manual}/local_yaml_02.yml | 0 tests/manual/overrides_01.yml | 14 ++ .../manual}/remote_json_01.yml | 0 .../manual}/remote_json_02.yml | 0 .../manual}/remote_text_01.yml | 0 tests/test_ansible_vault_password_file.py | 36 +++ tests/test_ansible_vault_source.py | 42 ++++ {src/tests => tests}/test_basic.py | 0 {src/tests => tests}/test_direct.py | 31 ++- tests/test_exec_source.py | 96 ++++++++ tests/test_keepass_source.py | 42 ++++ tests/test_local_ini.py | 119 ++++++++++ {src/tests => tests}/test_local_json.py | 67 ++++-- tests/test_local_text.py | 88 ++++++++ tests/test_local_yaml.py | 96 ++++++++ tests/test_override.py | 41 ++++ .../test_remote_source.py | 31 ++- 137 files changed, 2697 insertions(+), 1951 deletions(-) create mode 100644 .changelog/0.3.1.toml delete mode 100644 .changelog/0.3.1.yml create mode 100644 .changelog/0.3.3.toml delete mode 100644 .changelog/0.3.3.yml create mode 100644 .changelog/0.3.4.toml delete mode 100644 .changelog/0.3.4.yml create mode 100644 .changelog/0.3.5.toml delete mode 100644 .changelog/0.3.5.yml create mode 100644 .changelog/0.3.6.toml delete mode 100644 .changelog/0.3.6.yml create mode 100644 .changelog/0.3.7.toml delete mode 100644 .changelog/0.3.7.yml create mode 100644 .changelog/0.3.8.toml delete mode 100644 .changelog/0.3.8.yml create mode 100644 .changelog/0.4.0.toml delete mode 100644 .changelog/0.4.0.yml create mode 100644 .changelog/0.5.1.toml create mode 100644 .flake8 delete mode 100644 .pylintrc create mode 100644 .safety-policy.yml delete mode 100644 CHANGELOG.md delete mode 100644 MANIFEST.in delete mode 100644 docs/.readthedocs-custom-steps.yml delete mode 100644 docs/.readthedocs-requirements.txt create mode 100644 docs/build.novella delete mode 100644 docs/configuration-parsers.md delete mode 100644 docs/configuration-selectors.md delete mode 100644 docs/configuration-sources.md delete mode 100644 docs/configuration.md create mode 100644 docs/content/assets/icons8-keepassxc.svg create mode 100644 docs/content/assets/terminal-128x128.png create mode 100644 docs/content/definition-attributes/ansible-vault-password-file.md create mode 100644 docs/content/definition-attributes/ansible-vault-password.md create mode 100644 docs/content/definition-attributes/exec.md create mode 100644 docs/content/definition-attributes/keepass-password.md create mode 100644 docs/content/definition-attributes/name.md create mode 100644 docs/content/definition-attributes/override.md create mode 100644 docs/content/definition-attributes/overview.md create mode 100644 docs/content/definition-attributes/parser.md create mode 100644 docs/content/definition-attributes/selector.md create mode 100644 docs/content/definition-attributes/source.md create mode 100644 docs/content/definition-attributes/value.md create mode 100644 docs/content/development.md create mode 100644 docs/content/examples/ansible-project.md create mode 100644 docs/content/examples/aws-credentials.md create mode 100644 docs/content/examples/debugging.md create mode 100644 docs/content/examples/terraform-aws-project.md create mode 100644 docs/content/index.md create mode 100644 docs/content/license.md create mode 100644 docs/content/project.md delete mode 100644 docs/development.md delete mode 100644 docs/examples-debug.md delete mode 100644 docs/examples-larger.md delete mode 100644 docs/examples-simple.md create mode 100644 docs/mkdocs.yml delete mode 100644 docs/project.md delete mode 100644 docs/pydoc-markdown.yml delete mode 100644 package.yml create mode 100644 pyproject.toml delete mode 100644 setup.py delete mode 100755 src/bin/env-alias delete mode 100755 src/bin/env-alias-generator delete mode 100644 src/env_alias/cli/entrypoints.py delete mode 100755 src/env_alias/env_alias.py delete mode 100644 src/env_alias/env_alias_generator.py create mode 100644 src/env_alias/exceptions.py delete mode 100644 src/env_alias/exceptions/__init__.py rename src/env_alias/{cli => lib}/__init__.py (100%) create mode 100644 src/env_alias/lib/definitions.py create mode 100644 src/env_alias/lib/generator.py create mode 100644 src/env_alias/lib/logger.py create mode 100644 src/env_alias/lib/selector.py create mode 100644 src/env_alias/lib/source.py create mode 100644 src/env_alias/main.py create mode 100644 src/env_alias/models/envalias_definition.py create mode 100644 src/env_alias/models/sourced_content.py rename docs/configuration-previous.md => src/env_alias/py.typed (100%) delete mode 100644 src/env_alias/utils/__init__.py delete mode 100644 src/env_alias/utils/config.py delete mode 100644 src/env_alias/utils/content.py delete mode 100644 src/env_alias/utils/logger.py delete mode 100644 src/env_alias/utils/selector.py delete mode 100644 src/tests/samples/local.ini delete mode 100644 src/tests/samples/local.json delete mode 100644 src/tests/samples/local.txt delete mode 100644 src/tests/samples/local.yml delete mode 100755 src/tests/test_exec.py delete mode 100755 src/tests/test_local_ini.py delete mode 100755 src/tests/test_local_text.py delete mode 100755 src/tests/test_local_yaml.py rename {src/tests => tests}/__init__.py (99%) create mode 100644 tests/data/envalias-ansible-vault-datafile-password.txt create mode 100644 tests/data/envalias-ansible-vault-datafile.vault create mode 100644 tests/data/envalias-keepass-database-password.txt create mode 100644 tests/data/envalias-keepass-database.kdbx create mode 100644 tests/manual/ansible_vault_01.yml create mode 100644 tests/manual/ansible_vault_password_file_01.yml rename {src/tests/configs => tests/manual}/direct_01.yml (100%) rename {src/tests/configs => tests/manual}/direct_02.yml (100%) rename {src/tests/configs => tests/manual}/direct_03.yml (100%) rename {src/tests/configs => tests/manual}/exec_01.yml (100%) rename {src/tests/configs => tests/manual}/exec_02.yml (100%) rename {src/tests/configs => tests/manual}/exec_03.yml (100%) rename {src/tests/configs => tests/manual}/exec_04.yml (82%) create mode 100644 tests/manual/exec_bad_01.yml create mode 100644 tests/manual/inputs_01.yml create mode 100644 tests/manual/keepass_01.yml rename {src/tests/configs => tests/manual}/local_ini_01.yml (100%) rename {src/tests/configs => tests/manual}/local_ini_02.yml (100%) rename {src/tests/configs => tests/manual}/local_ini_03.yml (100%) rename {src/tests/configs => tests/manual}/local_json_01.yml (100%) rename {src/tests/configs => tests/manual}/local_json_02.yml (100%) rename {src/tests/configs => tests/manual}/local_text_01.yml (100%) rename {src/tests/configs => tests/manual}/local_text_02.yml (100%) rename {src/tests/configs => tests/manual}/local_yaml_01.yml (100%) rename {src/tests/configs => tests/manual}/local_yaml_02.yml (100%) create mode 100644 tests/manual/overrides_01.yml rename {src/tests/configs => tests/manual}/remote_json_01.yml (100%) rename {src/tests/configs => tests/manual}/remote_json_02.yml (100%) rename {src/tests/configs => tests/manual}/remote_text_01.yml (100%) create mode 100755 tests/test_ansible_vault_password_file.py create mode 100755 tests/test_ansible_vault_source.py rename {src/tests => tests}/test_basic.py (100%) rename {src/tests => tests}/test_direct.py (63%) create mode 100755 tests/test_exec_source.py create mode 100755 tests/test_keepass_source.py create mode 100755 tests/test_local_ini.py rename {src/tests => tests}/test_local_json.py (51%) create mode 100755 tests/test_local_text.py create mode 100755 tests/test_local_yaml.py create mode 100755 tests/test_override.py rename src/tests/test_remote.py => tests/test_remote_source.py (68%) diff --git a/.changelog/0.3.1.toml b/.changelog/0.3.1.toml new file mode 100644 index 0000000..d7883ef --- /dev/null +++ b/.changelog/0.3.1.toml @@ -0,0 +1,7 @@ +release-date = "2020-12-13" + +[[entries]] +id = "c03537a1-23a1-4765-a0e1-76e8f851b375" +type = "feature" +description = "Major project rearrangement, with full documentation" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.1.yml b/.changelog/0.3.1.yml deleted file mode 100644 index 3ad6559..0000000 --- a/.changelog/0.3.1.yml +++ /dev/null @@ -1,6 +0,0 @@ -release_date: '2020-12-13' -changes: -- type: feature - component: general - description: Major project rearrangement, with full documentation - fixes: [] diff --git a/.changelog/0.3.3.toml b/.changelog/0.3.3.toml new file mode 100644 index 0000000..c3afa17 --- /dev/null +++ b/.changelog/0.3.3.toml @@ -0,0 +1,7 @@ +release-date = "2020-12-13" + +[[entries]] +id = "69de0ed6-ef5e-4137-bf24-cbfb911d81b5" +type = "fix" +description = "Fix typo in package entrypoint" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.3.yml b/.changelog/0.3.3.yml deleted file mode 100644 index 1d5e279..0000000 --- a/.changelog/0.3.3.yml +++ /dev/null @@ -1,6 +0,0 @@ -release_date: '2020-12-13' -changes: -- type: fix - component: general - description: Fix typo in package entrypoint - fixes: [] diff --git a/.changelog/0.3.4.toml b/.changelog/0.3.4.toml new file mode 100644 index 0000000..eadbb19 --- /dev/null +++ b/.changelog/0.3.4.toml @@ -0,0 +1,7 @@ +release-date = "2020-12-14" + +[[entries]] +id = "2a344d45-7195-4491-9e25-0a0631dbaf85" +type = "fix" +description = "Fixes bug with null/none return types from exec and re-arranges test input data files" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.4.yml b/.changelog/0.3.4.yml deleted file mode 100644 index 695cabb..0000000 --- a/.changelog/0.3.4.yml +++ /dev/null @@ -1,7 +0,0 @@ -release_date: '2020-12-14' -changes: -- type: fix - component: general - description: Fixes bug with null/none return types from exec and re-arranges test - input data files - fixes: [] diff --git a/.changelog/0.3.5.toml b/.changelog/0.3.5.toml new file mode 100644 index 0000000..b68a6ac --- /dev/null +++ b/.changelog/0.3.5.toml @@ -0,0 +1,7 @@ +release-date = "2020-12-14" + +[[entries]] +id = "f38dbde7-4546-4373-b72b-9b5334c59e95" +type = "fix" +description = "fixes broken merge" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.5.yml b/.changelog/0.3.5.yml deleted file mode 100644 index ef17932..0000000 --- a/.changelog/0.3.5.yml +++ /dev/null @@ -1,6 +0,0 @@ -release_date: '2020-12-14' -changes: -- type: fix - component: general - description: fixes broken merge - fixes: [] diff --git a/.changelog/0.3.6.toml b/.changelog/0.3.6.toml new file mode 100644 index 0000000..987d44f --- /dev/null +++ b/.changelog/0.3.6.toml @@ -0,0 +1,7 @@ +release-date = "2021-06-05" + +[[entries]] +id = "7a0b5694-27bb-45af-a6dd-bf043fe26184" +type = "fix" +description = "migration from TravisCI to Github actions" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.6.yml b/.changelog/0.3.6.yml deleted file mode 100644 index 51657d5..0000000 --- a/.changelog/0.3.6.yml +++ /dev/null @@ -1,6 +0,0 @@ -release_date: '2021-06-05' -changes: -- type: fix - component: general - description: migration from TravisCI to Github actions - fixes: [] diff --git a/.changelog/0.3.7.toml b/.changelog/0.3.7.toml new file mode 100644 index 0000000..bdb9fc7 --- /dev/null +++ b/.changelog/0.3.7.toml @@ -0,0 +1,7 @@ +release-date = "2022-03-29" + +[[entries]] +id = "ed680068-13b2-490d-8265-f524b84f194a" +type = "fix" +description = "Black code cleanup; Add pylint; fix various pylint warnings" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.7.yml b/.changelog/0.3.7.yml deleted file mode 100644 index 800d523..0000000 --- a/.changelog/0.3.7.yml +++ /dev/null @@ -1,5 +0,0 @@ -changes: -- type: fix - component: general - description: Black code cleanup; Add pylint; fix various pylint warnings -release_date: '2022-03-29' diff --git a/.changelog/0.3.8.toml b/.changelog/0.3.8.toml new file mode 100644 index 0000000..21db193 --- /dev/null +++ b/.changelog/0.3.8.toml @@ -0,0 +1,7 @@ +release-date = "2022-03-29" + +[[entries]] +id = "229aa4d2-73d2-4095-bb1e-03f72e6b857c" +type = "fix" +description = "Fixes bug with an early return preventing more than one env-alias setting; changes the old Logger class out for something a little more standard; move the readdocs-custom-steps file out of the way" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.3.8.yml b/.changelog/0.3.8.yml deleted file mode 100644 index ed5c501..0000000 --- a/.changelog/0.3.8.yml +++ /dev/null @@ -1,7 +0,0 @@ -changes: -- type: fix - component: general - description: Fixes bug with an early return preventing more than one env-alias setting; - changes the old Logger class out for something a little more standard; move the - readdocs-custom-steps file out of the way -release_date: '2022-03-29' diff --git a/.changelog/0.4.0.toml b/.changelog/0.4.0.toml new file mode 100644 index 0000000..97ade84 --- /dev/null +++ b/.changelog/0.4.0.toml @@ -0,0 +1,7 @@ +release-date = "2022-03-29" + +[[entries]] +id = "d6aa2f98-c752-46ac-a6ac-c1f0121d3973" +type = "fix" +description = "Adds minor feature to check which version of env-alias is installed" +author = "ndejong@threatpatrols.com" diff --git a/.changelog/0.4.0.yml b/.changelog/0.4.0.yml deleted file mode 100644 index 2c7e28b..0000000 --- a/.changelog/0.4.0.yml +++ /dev/null @@ -1,5 +0,0 @@ -changes: -- type: fix - component: general - description: Adds minor feature to check which version of env-alias is installed -release_date: '2022-03-29' diff --git a/.changelog/0.5.1.toml b/.changelog/0.5.1.toml new file mode 100644 index 0000000..8dd6598 --- /dev/null +++ b/.changelog/0.5.1.toml @@ -0,0 +1,7 @@ +release-date = "2024-09-21" + +[[entries]] +id = "21e601b7-bf89-4861-8c61-f7359aa43da0" +type = "refactor" +description = "Large upgrade and improvements to env-alias" +author = "ndejong@threatpatrols.com" diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ef0e63a --- /dev/null +++ b/.flake8 @@ -0,0 +1,23 @@ +[flake8] +max-line-length = 120 + +# Black can yield formatted code that triggers these Flake8 warnings. +ignore = + + # Line break occurred before a binary operator (W503) - https://www.flake8rules.com/rules/W503.html + W503 + + # Line break occurred after a binary operator (W504) - https://www.flake8rules.com/rules/W504.html + W504 + + # E501 line too long (151 > 120 characters) - https://www.flake8rules.com/rules/E501.html + E501 + +per-file-ignores = + + # Module imported but unused (F401) - https://www.flake8rules.com/rules/F401.html + tests/**.py: F401 + + # W293 blank line contains whitespace - https://www.flake8rules.com/rules/W293.html + # W291 trailing whitespace - https://www.flake8rules.com/rules/W291.html + src/env_alias/main.py: W293, W291 diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml index a336e43..f6d46d5 100644 --- a/.github/workflows/build-tests.yml +++ b/.github/workflows/build-tests.yml @@ -4,53 +4,39 @@ on: [push] jobs: - build: + test: + name: "Test package" runs-on: ubuntu-latest - strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] - + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - name: Install actions/checkout@v2 - uses: actions/checkout@v2 - - - name: Install actions/setup-python@v2 for ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Python version - run: python -c "import sys; print(sys.version)" + - uses: actions/checkout@v4 + - uses: NiklasRosenstein/slap@gha/install/v1 + with: { version: "1.12.0" } - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade pyyaml - - - name: flake8 lint - run: | - python -m pip install --upgrade flake8 - # https://flake8.pycqa.org/en/latest/user/error-codes.html - # https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes - flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 ./src --count --exit-zero --max-complexity=20 --max-line-length=120 --statistics + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: { python-version: "${{ matrix.python-version }}" } - - name: python setup.py clean build + - name: Install required keepassxc run: | - python -m pip install --upgrade wheel setuptools - python setup.py clean build + sudo apt-get -y update + sudo apt-get install -y keepassxc - - name: pytest + - name: Create a venv to operate within using slap-cli run: | - python -m pip install --upgrade pytest - pytest + slap venv --create tester - - name: python setup.py install + - name: Install, test and build a package to install run: | - python setup.py install + slap release --validate + slap install --use-venv=tester + slap test --use-venv=tester + mkdir build-tester + slap publish --build-directory build-tester --dry - - name: which env-alias + - name: Install the package from whl and run the application run: | - which env-alias - which env-alias-generator + pip install build-tester/*.whl + env-alias --version diff --git a/.gitignore b/.gitignore index ff07146..6927f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,126 +1,30 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg +*.py[cod] +*.egg-info *.egg -MANIFEST -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ +venv*/ +.venv*/ +.idea/ .pytest_cache/ +__pycache__/ +.pytest*/ -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject +dev/ +dev.py +samples/ +docs/_site -# mkdocs documentation -/site +dist/ +build/ +packages/ -# mypy -.mypy_cache/ +/.vscode +/dist +/build +.venv/ +*.egg-info/ +poetry.lock .dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -.idea +profiler.html diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 2e9daa5..0000000 --- a/.pylintrc +++ /dev/null @@ -1,18 +0,0 @@ -# This file was automatically generated via Shut. -# Run `shut pkg update` to regenerate it. - -[MASTER] -disable = - attribute-defined-outside-init, - bad-continuation, - invalid-name, - missing-class-docstring, - missing-function-docstring, - missing-module-docstring, - no-init, - no-else-continue, - no-else-return, - too-few-public-methods, - unnecessary-lambda -indent-string=' ' -max-line-length = 120 diff --git a/.readthedocs.yml b/.readthedocs.yml index 3645394..34ad264 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,10 +1,23 @@ + version: 2 -mkdocs: {} # tell readthedocs to use mkdocs +mkdocs: + configuration: "docs/mkdocs.yml" + +build: + os: ubuntu-22.04 + tools: + python: "3.10" + jobs: + pre_build: + - pip install mkdocs-material==9.1.21 # https://pypi.org/project/mkdocs-material/#history + - pip install pydoc-markdown==4.8.2 # https://pypi.org/project/pydoc-markdown/#history + - pip install novella==0.2.6 # https://pypi.org/project/novella/#history + - cd docs && novella --base-url /en/latest/ + post_build: + - rm -Rf $READTHEDOCS_OUTPUT/html + - mv docs/_site $READTHEDOCS_OUTPUT/html -python: - version: 3.8 - install: - - method: pip - path: . - - requirements: docs/.readthedocs-requirements.txt +# README: the post_build steps give effect to removing the content generated by the readthedocs build system +# whereby they do a "python -m mkdocs build ..."; there seems to be no way to stop that build step running so +# as a post_build action we remove and move our novella rendered content into place diff --git a/.safety-policy.yml b/.safety-policy.yml new file mode 100644 index 0000000..a31fd78 --- /dev/null +++ b/.safety-policy.yml @@ -0,0 +1,13 @@ +# Safety Security and License Configuration file +# https://docs.pyup.io/docs/safety-20-policy-file + +security: + ignore-cvss-severity-below: 0 + ignore-cvss-unknown-severity: False + ignore-vulnerabilities: + 70612: # Vulnerability found in jinja2 version 3.1.4 - https://data.safetycli.com/v/70612/97c + expires: '2024-12-30' + 66742: # Vulnerability found in black version 23.12.1 - https://data.safetycli.com/v/66742/97c + expires: '2024-12-30' + + continue-on-vulnerability-error: False diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 506b2cd..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -## 0.3.5 (2020-12-14) - -* __general__ - * **fix**: fixes broken merge - -## 0.3.4 (2020-12-14) - -* __general__ - * **fix**: Fixes bug with null/none return types from exec and re-arranges test - input data files - -## 0.3.3 (2020-12-13) - -* __general__ - * **fix**: Fix typo in package entrypoint - -## 0.3.1 (2020-12-13) - -* __general__ - * **feature**: Major project rearrangement, with full documentation - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 2ef9f14..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -# This section is auto-generated by Shut. DO NOT EDIT { -include package.yml -include LICENSE -include README.md -# } diff --git a/README.md b/README.md index bf60279..a324315 100644 --- a/README.md +++ b/README.md @@ -6,58 +6,101 @@ [![Read the Docs](https://img.shields.io/readthedocs/env-alias)](https://env-alias.readthedocs.io) ![License](https://img.shields.io/github/license/ndejong/env-alias.svg) -Powerful helper utility to create shell alias commands to easily set collections of environment -variables often with secret values from a variety of data-sources and data-formats. +Env Alias is an environment variable swiss-army-knife that enables loading complex collections +of environment variables from a variety of sources only when you require them, thus reducing risks +in working with sensitive environment values. -Enables complex environment setups to be stored in source-control without secret values which makes -it particularly useful in loading secret values into containerized service environments and developer -environments alike. +A variety of data-formats are supported including **JSON**, **YAML**, **Keepass**, **Ansible Vault**, +**Plaintext** and **Ini**-config where these formats can be sourced from the local-filesystem, +http-remote or generated through shell-command exec output. + +For example setting an Ansible-vault password file and loading AWS access credentials from values stored +in a git project based Keepass file: +```yaml +env-alias: + + MYPROJECT_KEEPASS_FILE: + name: null # prevents this value being assigned into env + exec: 'echo "$(git rev-parse --show-toplevel)/secrets/myproject-keepass.kdbx"' + + MYPROJECT_KEEPASS_PASSPHRASE: + source: "" # obtain value from user-input using getpass method + override: false # if this env-value exists then skip setting again + + MYPROJECT_ANSIBLE_VAULT_PASSWORD: + name: null # prevents this value being assigned into env + source: "env:MYPROJECT_KEEPASS_FILE" + selector: "myproject-name/ansible-vault-entry-name:Password" # select an item from Keepass file + keepass_password: "env:MYPROJECT_KEEPASS_PASSPHRASE" + + ANSIBLE_VAULT_PASSWORD_FILE: + ansible_vault_password: "env:MYPROJECT_ANSIBLE_VAULT_PASSWORD" # NB: see docs how this gets managed + ansible_vault_password_file: true # invoke special helper that renders an Ansible Vault password file + + AWS_SECRET_ACCESS_KEY: + source: "env:MYPROJECT_KEEPASS_FILE" + selector: "myproject-name/aws-entry-name:Password" + keepass_password: "env:MYPROJECT_KEEPASS_PASSPHRASE" + + AWS_ACCESS_KEY_ID: + source: "env:MYPROJECT_KEEPASS_FILE" + selector: "myproject-name/aws-entry-name:Username" + keepass_password: "env:MYPROJECT_KEEPASS_PASSPHRASE" + +``` + +The above example sets the environment variable `MYPROJECT_KEEPASS_PASSPHRASE` with user input using +the `getpass` Python module only if not already set (`override=false`). This environment value is then +used as the `keepass` passphrase to open a Keepass file where values are then selected and exported +into the shell environment. + +Substantially more complex env-alias definitions can be created. + +By naming your env-aliases with an easy to remember prefix such as `env-` it is also possible to +leverage shell **tab-completion** thus making it easier to find the env-alias definitions created +for your project or other use-case situation. ## Features -* Data sources: local-file, http-remote, exec-stdout or directly-set -* Source file formats: `text`, `ini`, `json`, `yaml` -* Select using jq-style selectors, xpath selectors or line-numbers -* Reference other env values within configuration -* Content-Type detection for http-remote data-sources to automatically assign appropriate parser -* Run exec helper commands without content assignment for creating setups -* Debug mode output to STDERR -* Easy installation using PyPI `pip` -* Plenty of documentation and examples - https://env-alias.readthedocs.io +Env Alias is enormously useful in working with large sets of environment variables from remote, encrypted +or otherwise secured data-sources. + +* Data sources: **local-files**, **http-remote** and stdout from an **exec** command-line. +* Source formats supported: **JSON**, **YAML**, **Keepass**, **Ansible Vault**, **Plaintext** and **Ini**-config. +* Select values using **jq** style selectors, **xpath** style selectors or **line-numbers**. +* 💥 Additional special handling for **Ansible Vault Password Files** that makes credential handling for **Ansible Vault** files substantially easier with reduced exposure risks. 💥 +* Self reference environment values in the definition file or from the existing system environment. +* Define variables with a `null` name to prevent them being exported into the system environment while still being available for self-reference within the env-alias definition; this is helpful when working with sensitive values that should not be available through the system environment. +* Ability to use `exec` commands to setup other project prerequisites or other project start conditions. +* Debug mode output to STDERR. +* Easy installation from PyPI. +* Plenty of documentation and examples - [https://env-alias.readthedocs.io](https://env-alias.readthedocs.io) ## Installation +Pip or pipx should be fine, we prefer pipx these days. ```shell -pip install env-alias +pipx install env-alias ``` ## Usage -This tool is typically invoked via an entry in `.bash_aliases` with an entry in the form:- +This tool is typically invoked using an entry in `.bash_aliases` with an entry of the form:- ```shell -eval $(env-alias env-project-awesome ~/path-to/project-awesome-alias.yml) +source <(env-alias ~/projects/awesome/env-awesome-vars.yml) ``` -This simple one-liner creates a command alias in the name `env-project-awesome` that subsequently invokes -an alias-generator that gives effect to the `.yml` configurations(s). - -By naming your alias commands with a common prefix such as `env-` it also becomes possible to leverage -shell tab-completion to quickly find aliases that implement the environment settings for your project, -use-case or other situation. +This simple `.bash_aliases` one-line entry creates the alias `env-awesome-project` by inferring the +alias-name from the filename, where this alias then invokes env-alias to set environment values +defined in `env-awesome-project.yml` -This mechanism is enormously useful in working with large sets of environment variables from encrypted -or otherwise secured data-sources which means an environment configuration can be easily committed to -source control without the secret values. - -Be sure to review the examples that make the benefits of this arrangement clear. - -### Version -Check which version of env-alias is installed +Alternatively, you might want to create the alias `awesome-envvars` which you could do as per - ```shell -env-alias --version +source <(env-alias awesome-envvars ~/projects/awesome/env-awesome-vars.yml) ``` + ## Project * Github - [github.com/ndejong/env-alias](https://github.com/ndejong/env-alias) * PyPI - [pypi.python.org/pypi/env-alias](https://pypi.python.org/pypi/env-alias/) * ReadTheDocs - [env-alias.readthedocs.io](https://env-alias.readthedocs.io) --- -Copyright © 2021 Nicholas de Jong +Copyright © (2020-2024) Nicholas de Jong diff --git a/docs/.readthedocs-custom-steps.yml b/docs/.readthedocs-custom-steps.yml deleted file mode 100644 index 7586d83..0000000 --- a/docs/.readthedocs-custom-steps.yml +++ /dev/null @@ -1,3 +0,0 @@ - -steps: - - pydoc-markdown --build --site-dir "$PWD/_build/html" -vv "$PWD/docs/pydoc-markdown.yml" diff --git a/docs/.readthedocs-requirements.txt b/docs/.readthedocs-requirements.txt deleted file mode 100644 index 062562a..0000000 --- a/docs/.readthedocs-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ - -pydoc-markdown==4.4.0 -readthedocs-custom-steps==0.5.1 diff --git a/docs/build.novella b/docs/build.novella new file mode 100644 index 0000000..9c7af3a --- /dev/null +++ b/docs/build.novella @@ -0,0 +1,17 @@ + +template "mkdocs" { + content_directory = "content" +} + +action "mkdocs-update-config" { + site_name = "Env Alias" + update '$.theme.features' add: ['navigation.sections'] + update '$.theme.features' add: ['content.code.copy'] + update '$.theme.palette' add: {'scheme': 'default', 'primary': 'orange'} + update '$.theme.icon' add: {'logo': 'octicons/terminal-24'} + update '$.theme' add: {'favicon': 'assets/terminal-128x128.png'} +} + +action "preprocess-markdown" { + use "pydoc" +} diff --git a/docs/configuration-parsers.md b/docs/configuration-parsers.md deleted file mode 100644 index aaed116..0000000 --- a/docs/configuration-parsers.md +++ /dev/null @@ -1,31 +0,0 @@ -# Env Alias - -## Parsers -If the parser is not defined, Env Alias will estimate the source content format based on filename -extension, either `.ini`, `.json`, `.yml` or `.yaml` otherwise the content will be treated as plain -text. - -Additionally, source content retrieved using a http-remote source will attempt to use the `Content-Type` -response header to estimate the content format, again if not otherwise configured. - -### Configuration Keyword Samples - -```yaml - parser: 'yaml' -``` -Use YAML content parsing - -```yaml - parser: 'json' -``` -Use JSON content parsing - -```yaml - parser: 'ini' -``` -Use ini/config content parsing - -```yaml - parser: 'text' -``` -Use plain-text content parsing diff --git a/docs/configuration-selectors.md b/docs/configuration-selectors.md deleted file mode 100644 index b90a93e..0000000 --- a/docs/configuration-selectors.md +++ /dev/null @@ -1,28 +0,0 @@ -# Env Alias - -## Selectors -Selectors make it possible to "pick" a value from the parsed content. - -Structured content formats (ini, yaml, json) support dot-notation (eg: `foo.0.bar`) and -brace-notation (eg: `foo[0]bar`) as data selectors. - -Text files support line-number selectors only. - -A special `none` (also `null`) selector exists which prevents any environment variable setting -which is useful when using a shell-exec and the output is not needed. - -### Configuration Keyword Samples -```yaml - selector: '.prefixes[1].ip_prefix' -``` -Select from a structured data file the second `ip_prefix` - -```yaml - selector: none -``` -Do not assign the selector response data to an environment variable. - -```yaml - selector: 2 -``` -Select the second line as might be done when working with text files. diff --git a/docs/configuration-sources.md b/docs/configuration-sources.md deleted file mode 100644 index e23bc21..0000000 --- a/docs/configuration-sources.md +++ /dev/null @@ -1,57 +0,0 @@ -# Env Alias - -## Sources -All Env Alias definitions must have a `source`, `exec` or `value` that sets the source content that -is subsequently passed to a parser. - -Four types of sources are available:- - * **source** (local) - any local file. - * **source** (remote) - any http-remote object available via a GET request. - * **exec** (stdout) - the STDOUT content from a shell-exec command. - * **value** (direct setting) - direct assignment of the value. - -It is additionally possible to assign the `source`, `exec` or `value` setting through an environment -variable itself by prefixing with `env:` this slightly unusual arrangement is useful when a source -needs to be set with content that should not appear in the Env Alias configuration itself. - -### Configuration Keyword Samples - -```yaml - source: "/etc/ssl/openssl.cnf" -``` -Full pathname to config - -```yaml - source: "~/.aws/credentials" -``` -Home-dir path to config - -```yaml - source: "https://ip-ranges.amazonaws.com/ip-ranges.json" -``` -Remote via http to config - -```yaml - source: "env:other_environment_variable" -``` -Source location set by the value of the `other_environment_variable` environment variable. - -```yaml - exec: "mkdir -p ~/.terraform.d/plugin-cache" -``` -Exec a shell-command to mkdir, the empty response would be ignored - -```yaml - exec: "head /dev/urandom | base64 - -w0 | tr -d "=/+" | head -c20" -``` -Exec a shell-command to generate a 20 character random value - -```yaml - exec: "ifconfig | grep 'inet ' | head -n1 | xargs | cut -d' ' -f2" -``` -Exec a shell-command to extract the first ip4-address returned by ifconfig - -```yaml - value: "foo" -``` -Directly set the content value to "foo" diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index adc448c..0000000 --- a/docs/configuration.md +++ /dev/null @@ -1,54 +0,0 @@ -# Env Alias - -Env Alias configuration files are YAML format files that define how their alias definitions behave. - -All Env Alias configurations MUST have an `env-alias:` top-level root. - -Environment variable names are defined by the second-level key name, such as `TF_PLUGIN_CACHE_DIR` as -shown in the short example below. - -The environment value itself is then defined using 3x main components:- - * **\[source|exec|value\]** - source content that is passed to the parser is specified using either a - `source`, `exec` or `value` setting. Content via `source` may be local-file or http-remote; content - via `exec` provides the content from STDOUT and; content can directly assigned using a `value` setting. - * **parser** - the parser to use for the source-content can be automatically estimated based on the - filename extension or the `Content-Type` header (if http-remote). However the parser can also be set - directly which is useful when the source content does not use an indicative filename extension which - is common with ini-config files. Env Alias provides 4x parsers, **INI**-config, **YAML**, **JSON** or - simple **TEXT** file formats. - * **selector** - the selector provides the ability to "pick" out a value from the parsed content. - Structured content formats (ini, yaml, json) support dot-notation (eg: `foo.0.bar`) and brace-notation - (eg: `foo[0]bar`) as data selectors. Text files support line-number selectors only. A special `none` - selector exists which prevents any environment variable setting which are useful when using a shell-exec - and the output is not needed. - -## Example -The example below shows an Env Alias configuration for setting up a Terraform environment. Notice that -all parts of the environment are easily established by calling a single alias name and that no secret -values are contained within. - * Environment variable `TF_VAR_aws_access_key_id` is set by reading the file `~/.aws/credentials` - and selecting the value from `account_name.aws_access_key_id` - * Environment variable `TF_VAR_aws_secret_access_key` is set in a similar manner. - * The path `~/.terraform.d/plugin-cache` is created and the shell exec stdout is not assigned to anything. - * Environment variable `TF_PLUGIN_CACHE_DIR` is set directly in-line to the value `~/.terraform.d/plugin-cache` - -```yaml -env-alias: - - TF_VAR_aws_access_key_id: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_access_key_id' - - TF_VAR_aws_secret_access_key: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_secret_access_key' - - TF_PLUGIN_CACHE_DIR_CREATE: - exec: 'mkdir -p ~/.terraform.d/plugin-cache' - selector: null - - TF_PLUGIN_CACHE_DIR: - value: '~/.terraform.d/plugin-cache' -``` diff --git a/docs/content/assets/icons8-keepassxc.svg b/docs/content/assets/icons8-keepassxc.svg new file mode 100644 index 0000000..85bf947 --- /dev/null +++ b/docs/content/assets/icons8-keepassxc.svg @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/docs/content/assets/terminal-128x128.png b/docs/content/assets/terminal-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..ba14878b6f22dc2d47a69994ad8c04a18e0afc2b GIT binary patch literal 1408 zcmZvbdpOg39LK-g*v)0@U|CIMgj`OEj1HTbu|hP2R2U**iCT#^rL1GR5s~JIa*A+@ zxm13VI5=(<<&uSioN}#P5@-F<^PKaX^E~hO=kt8te|}!iTj=WIpoCq61pp|q92vW0 zoBz#dq-@sN_%{OpI}>VW=gP9P)8a-&28SLB0zm&#+$C$rIvS~dKRaJr?c7oP;YQbN zqong_6Q4wFcXS#3uJVTTc-t#yb?WQgxW<(EQm+{9;jWjp9(Xw_?YhQw)xCL7S9B8z zo7a)%hWZ~h^lzhuxMu{%h+vr!S?>brEhC=w?S}Lutt2Aa^nS7>YkTdnll^3M_QEPkErFQB@hCf&gc`)KN2R2LO6%au(_hdq(mR@c}?^isap?E-sr)UzBU%S zxwO-(-b=kxu222vYqX?^_sq-LLvhrM4c;2O$=?3gzdzKtsKdJaUH0rdGqWzzP3;PW zh%SnIrow@p#}kFSLkEH$|)q}FS9(08WA=}DZ}Yi z+wsvmuVADC&VHt=6r`{RW`fB2A)vS5lv4F7)lGnpV&abiV`v8%Z2|Zm(5yX!+hJ+s z37CHWquB>0Gl3ljqZM8-qpb-BKRgEB3yOZFtLDumD@1;`+#S5^NiLiUlt3?gC?HPp z_yHn0`L4cGFqe%QKnq1%#55Xu4FqCxofDYe`aIPG_Q_vq{vkd2kVm|l6-Y3ru z=yRYwrtx(myomypK2!Dd&pxJL-2hx$!p>N-$?YA?J1%K0&YN4^+#=Ub|2n6$uStIW zWaBgAJ#C5WXt;NU$jnwpaO^jA8hM4Zl%JAwb(|mhSeQ%-6UE}u7ebe;pk^ZPa~tTj zMxQBzVwWEmf4vq&;-^73Q}tG&J;l<{J`1jViA6lq3AFLsntwv;O0_!jIiiQWK*b78 z&7@mI+91RmY8(_}3Y)^k literal 0 HcmV?d00001 diff --git a/docs/content/definition-attributes/ansible-vault-password-file.md b/docs/content/definition-attributes/ansible-vault-password-file.md new file mode 100644 index 0000000..dc93162 --- /dev/null +++ b/docs/content/definition-attributes/ansible-vault-password-file.md @@ -0,0 +1,44 @@ +# ansible_vault_password_file + +The `ansible_vault_password_file` definition-attribute is a special attribute that causes a standard Ansible Vault +password-file to be generated as per [docs.ansible.com](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#envvar-ANSIBLE_VAULT_PASSWORD_FILE) documentation. + +When this is used to set Ansible variable `ANSIBLE_VAULT_PASSWORD_FILE` you gain the ability to easily invoke +ansible-vault without further Ansible configuration or other Ansible environment settings. + +### Details + +Of note is that the ansible-password-file rendering uses random file names and hash-of-source-name to create consistent +but difficult to guess environment names making it harder to target specific environment values. + +```commandline +$ env | grep ANSIBLE_VAULT_PASSWORD_FILE +ANSIBLE_VAULT_PASSWORD_FILE=/tmp/igxrsfnrsxig + +$ cat /tmp/igxrsfnrsxig +#!/bin/sh +echo "${E25AF8C1096A}" + +$ env | grep E25AF8C1096A +E25AF8C1096A=zPrT1z8yYTBV5q5l7jahGoQf79fcu9qtD4ERM3wB +``` + +In the above example - + +* The env-var `ANSIBLE_VAULT_PASSWORD_FILE` points to a random filename `/tmp/igxrsfnrsxig` located in the system temp path +* The Ansible password-file is a standard format executable that echos out another environment value as per Ansible [documentation](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#envvar-ANSIBLE_VAULT_PASSWORD_FILE) +* The environment name `E25AF8C1096A` gets generated based on a salted SHA256 of the source attribute name (not the value itself) +* Finally, the value for the vault-password is exposed on the environment variable `E25AF8C1096A` + +The above is achieved using an env-alias definition as simple as - +```yaml + ANSIBLE_VAULT_PASSWORD_FILE: + ansible_vault_password: "some-secret-value" + ansible_vault_password_file: true +``` + +!!!warning + + Typically, the `ansible_vault_password` value should never be set using an in-the-clear value as shown above, you + should use prior steps to obtain this value safely/securely such as from user-input using `` or load + from a Keepass file or other appropriate mechanism. diff --git a/docs/content/definition-attributes/ansible-vault-password.md b/docs/content/definition-attributes/ansible-vault-password.md new file mode 100644 index 0000000..758cfbc --- /dev/null +++ b/docs/content/definition-attributes/ansible-vault-password.md @@ -0,0 +1,67 @@ +# ansible_vault_password + +The `ansible_vault_password` definition-attribute is used to define a password used to open an Ansible Vault file. + +This value can be used in two contexts - + +1. Can be used as an ansible-vault-file `source` to then select an item for a regular env-alias + definition - see the **Regular usage** below. +2. Can be used with the `ansible_vault_password_file` attribute that invokes a special helper for generating Ansible + Vault password-files; this is super-helpful when working with Ansible, see the **Special helper usage** below. + + +## Regular usage +```yaml +env-alias: + + EXAMPLE_ANSIBLE_VAULT_PASSWORD: + name: null + source: "" + override: false # if this env-value exists then skip setting again + + EXAMPLE_VALUE: + source: "~/My Files/ansible-vault-datafile.vault" + selector: "all/vars/vault/my_example_value" + ansible_vault_password: "env:EXAMPLE_ANSIBLE_VAULT_PASSWORD" +``` +The example above - + +* the ansible-password variable input is taken from user input using Python getpass. +* the user-input is _not_ exported into the system environment (`name: null`) and only exists internally within env-alias. +* the user-input is skipped if the env-value is already set. +* the environment value `EXAMPLE_VALUE` is set using an item located at `all/vars/vault/my_example_value` within the + ansible-vault file `ansible-vault-datafile.vault`. + + +## Special helper usage +The special Ansible helper is invoked by setting `ansible_vault_password_file` to true. + +This special helper makes working with Ansible Vault files considerably easier and tidier by self generating the +required (executable) Ansible Vault Password File. + +The helper also generates "hard-to-guess" filenames and environment values that are derived from multiple sha256 +rounds of the password with salt added. See the [docs](/definition-attributes/ansible_vault_password_file) for more +details on this mechanism. + +```yaml +env-alias: + + EXAMPLE_ANSIBLE_VAULT_PASSWORD: + name: null + source: "" + + ANSIBLE_VAULT_PASSWORD_FILE: + ansible_vault_password: "env:EXAMPLE_ANSIBLE_VAULT_PASSWORD" + ansible_vault_password_file: true +``` + +The example above - + +* the ansible-password variable input is taken from user input using Python `getpass`. +* the user-input is _not_ exported into the system environment (`name: null`) and only exists internally within env-alias. +* the environment variable `ANSIBLE_VAULT_PASSWORD_FILE` is set to a value that points to a generated executable file + that uses random file-names and hard-to-guess variable-names. + +This arrangement then allows the user to interact with `ansible-vault` without any further configuration or effort to +manage the credential. You may additionally choose to store the Ansible Vault password in a Keepass database that can +be easily chained together here. diff --git a/docs/content/definition-attributes/exec.md b/docs/content/definition-attributes/exec.md new file mode 100644 index 0000000..c9e37bf --- /dev/null +++ b/docs/content/definition-attributes/exec.md @@ -0,0 +1,70 @@ +# exec + +The `exec` definition-attribute can be used to obtain values from STDOUT when executing a shell command. All the +usual parsers and selectors are available as they are with other source types. + +!!! warning + + It should be obvious, however, shell execution hazards and their appropriate precautions apply with this functionality. + + + +### Example - curl + +For example using `exec` it is possible to set external values by calling curl + +```yaml +env-alias: + EXAMPLE: + exec: "curl -s https://ip-ranges.amazonaws.com/ip-ranges.json" + parser: "json" + selector: ".prefixes[1].ip_prefix" +``` + +This example is somewhat redundant because env-alias will perform a http-get request for any source definition +that looks like a URL anyway. + + +### Example - mkdir + +This functionality can be useful in other ways too, such as making sure resources exist before loading an +environment, for example create a path and skip setting the env variable. + +```yaml +env-alias: + EXAMPLE: + name: null + exec: "mkdir -p ~/.terraform.d/plugin-cache" +``` + + +### Example - random string + +Another example that invokes a shell-command to generate a 20 character random value, by default the +source-type is `text` and the selector will take the first line so no further definition is required here. + +```yaml +env-alias: + EXAMPLE: + exec: "head /dev/urandom | base64 - -w0 | tr -d "=/+" | head -c20" +``` + + +If we expand this into long-form with its parser and selector, we'd get the same thing. +```yaml +env-alias: + EXAMPLE: + exec: "head /dev/urandom | base64 - -w0 | tr -d "=/+" | head -c20" + parser: "text" + selector: 1 +``` + +### Example - host ip addr + +Another example to obtain the first ip-address on the first interface of the host + +```yaml +env-alias: + EXAMPLE: + exec: "ip -json addr | jq -r .[1].addr_info[0].local" +``` diff --git a/docs/content/definition-attributes/keepass-password.md b/docs/content/definition-attributes/keepass-password.md new file mode 100644 index 0000000..77ce983 --- /dev/null +++ b/docs/content/definition-attributes/keepass-password.md @@ -0,0 +1,46 @@ +# keepass_password + +The `keepass_password` definition-attribute is used to send a password through keepass-cli when opening a Keepass +file that hence makes it possible select values inside Keepass files. + + +### Example - keepass + +```yaml +env-alias: + + MYPROJECT_KEEPASS_PASSPHRASE: + source: "" # obtain value from user-input using getpass method + override: false # if this env-value exists then skip setting again + + MYPROJECT_KEEPASS_FILE: + name: null # prevent this value being assigned into env with this name + exec: 'echo "$(git rev-parse --show-toplevel)/secrets/myproject-keepass.kdbx"' + + MYPROJECT_SECRET_VALUE: + source: "env:MYPROJECT_KEEPASS_FILE" + selector: "keepass-folder-name/keepass-entry-name:Password" + keepass_password: "env:MYPROJECT_KEEPASS_PASSPHRASE" +``` + +The example above demonstrates how it is possible to collect a keepass password into an environment variable +with user-input and use this to open and access contents within a Keepass file. + +**NB:** case-sensitive "Password" expression in the selector expression, similarly, "Username" is case-sensitive too. + + +## Under the hood +Under the hood env-alias wraps a command line to exec a keepass-cli command as shown - + +```python +random_envvar = "".join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") for i in range(16)) +os.environ[random_envvar] = password + +command_line = ( + f' printf "${"{" + random_envvar + "}"}" | "{keepassxc_cli}" show ' + f'--quiet --show-protected --attributes "{keepass_attribute}" "{str(filename)}" "{keepass_path}"' +) + +execute_content = EnvAliasSource.execute(command_line) +os.unsetenv(random_envvar) +``` diff --git a/docs/content/definition-attributes/name.md b/docs/content/definition-attributes/name.md new file mode 100644 index 0000000..e6e707f --- /dev/null +++ b/docs/content/definition-attributes/name.md @@ -0,0 +1,38 @@ +# name + +The `name` definition-attribute is used to name (or rename) the environment variable name that is otherwise +taken from the env-alias definition key name. + +More importantly, the `name` attribute can be set to `null` in which case the variable will be treated as an +internal runtime only variable that does not get exposed into the environment; this is useful when passing +secrets in-between definitions. + + +### Example - simple + +```yaml +env-alias: + DEFINITION_01: + name: "MYPROJECT_ENV_VAR_01" + value: "hello world a" + + DEFINITION_02: + name: "MYPROJECT_ENV_VAR_02" + value: "Hello World B" +``` + + +### Example - hidden variable + +The example below demonstrates using a `null` name for `MYPROJECT_HIDDEN_ENV_VAR` that does not get assigned into +the environment but can still be referenced in the subsequent `MYPROJECT_ENV_VAR` definition. + +```yaml +env-alias: + MYPROJECT_HIDDEN_ENV_VAR: + name: null + exec: "date +%s.%N" + + MYPROJECT_ENV_VAR: + value: "env:MYPROJECT_HIDDEN_ENV_VAR" +``` diff --git a/docs/content/definition-attributes/override.md b/docs/content/definition-attributes/override.md new file mode 100644 index 0000000..3a5710e --- /dev/null +++ b/docs/content/definition-attributes/override.md @@ -0,0 +1,22 @@ +# override + +The `override` definition-attribute is used to skip setting an environment variable if an environment value +already exists. + +This is helpful when you only want to obtain user input once per terminal session. + +### Example - override + +```yaml +env-alias: + + MYPROJECT_USER_PASSWORD: + source: "" # obtain value from user-input using getpass method + override: false # if this env-value exists then skip setting again + + MYPROJECT_USER_INPUT: + source: "" # obtain value from user-input + override: false # if this env-value exists then skip setting again + +``` + diff --git a/docs/content/definition-attributes/overview.md b/docs/content/definition-attributes/overview.md new file mode 100644 index 0000000..a1fdd39 --- /dev/null +++ b/docs/content/definition-attributes/overview.md @@ -0,0 +1,59 @@ +# Overview + +Env Alias definition files are YAML format files that define how the value for each environment variable is generated. + +* All Env Alias definition files MUST have an `env-alias` top-level root. +* Environment variable names are defined by their key name, or their `name` attribute. +* Each environment-variable definition uses attributes that define how their values are + generated or obtained. + +Conceptually, values for the environment variables are generated in three steps - + + 1. Content from source: this can be from the local-filesystem, exec-command, remote-http, in-line etc. + 2. Parse the source content: serializing the content from its respective format YAML, JSON, INI, TEXT etc. + 3. Select the item from the parsed content: using a `jq` style selector (or xpath selector) select the value required. + +Modifiers and special cases (e.g. Ansible Password Files) are possible, however the above 1,2,3 steps are usual. + +The following definition attributes are available - + +* [**source**](../source) - defines a source of content to be sent to the parser. The `source` attribute supports + some special values including `` that invokes the Python getpass module, regular `` that does what + you'd expect from STDIN, the prefix `env:` can be used to import the source definition from another environment + variable. Source values beginning with `http` are treated as remote-http that invoke a GET request to retrieve. + +* [**parser**](../parser) - the parser (or deserializer) is automatically estimated based on source filename + extensions or the `Content-Type` header (if http-remote) and defaults to TEXT if nothing is determined. This + behaviour is easily overridden by defining `ini`, `yaml`, or `json` as the parser; additionally, a `none` parser is + available that does a raw pass-through without any parsing. + +* [**selector**](../selector) - the selector provides the ability to "select" a value from the parsed content. Selectors + support basic forms of dot-notation (eg: `foo.0.bar`), brace-notation (eg: `foo[0]bar`) and slash-notation (eg: + `foo/0/bar`). Text files support line-number selectors for the full line only. Defining the selector as `null` + prevents the environment variable from being exported into the system environment that is similar to setting + the `name` as null. + +* [**name**](../name) - override the definition key-name and use this name instead; defining the name as `null` (without + quotes) causes the variable to become internal-only within the definition file and will not be exported into the + system environment. + +* [**value**](../value) - directly set the value in-line; values prefixed with `env:` can be used to import values from + another environment variable. + +* [**exec**](../exec) - defines a command-line to exec where the STDOUT is returned as the source content; any nonzero + exit-code will raise an exception that will exit with an error from the command-line STDERR output. + +* [**override**](../override) - a true/false attribute that defines if Env Alias will override any existing environment + value; by default set to `True`. + +* [**keepass_password**](../keepass-password) - used to define a password to open a Keepass database `.kdbx` file as + a `source` definition; Additionally, a `selector` in the form `group/subgroup/entryname:Password` is required to + obtain the desired Keepass value. + +* [**ansible_vault_password**](../ansible-vault-password) - used to define a password to open an Ansible Vault + file; this attribute can be used _either_ to open and select values from an Ansible Vault -or- invoke the special + ansible-vault-password-file helper. + +* [**ansible_vault_password_file**](../ansible-vault-password-file) - a true/false attribute that is used together + with the `ansible_vault_password` attribute to automatically create an [Ansible Vault Password File](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#envvar-ANSIBLE_VAULT_PASSWORD_FILE) (executable + file style) setups; 💥 this super helpful and a favorite feature! 💥 diff --git a/docs/content/definition-attributes/parser.md b/docs/content/definition-attributes/parser.md new file mode 100644 index 0000000..505e4df --- /dev/null +++ b/docs/content/definition-attributes/parser.md @@ -0,0 +1,53 @@ +# parser + +The parser defines the deserializer used to parse the source-content. + +If the parser is not explicitly defined, EnvAlias will estimate what to use based on filename +extension or the http-remote `Content-Type` response header. + +All other source-content is treated as plain text. + + +### Example - ini +Parse the `aws_access_key_id` from a standard AWS credentials file. +```yaml +env-alias: + AWS_ACCESS_KEY_ID: + source: "~/.aws/credentials" + parser: ini + selector: "default.aws_access_key_id" +``` + + +### Example - yaml +Parse a value from `/foo/bar/data` in the file `/tmp/foobar.data` using an explict `yaml` parser because the filename +extension does not indicate a `.yaml` file. +```yaml +env-alias: + EXAMPLE: + source: "/tmp/foobar.data" + parser: yaml + selector: "/foo/bar/data" +``` + + +### Example - json +Parse a value from `/foo/bar/data` in the file `/tmp/foobar.json` using an inferred `JSON` parser because the filename +infers the json file type. +```yaml +env-alias: + EXAMPLE: + source: "/tmp/foobar.json" + selector: "/foo/bar/data" +``` + + +### Example - text +Read the source as text even though the filename indicates `.json` content type. +```yaml +env-alias: + EXAMPLE: + source: "/tmp/foobar.json" + parser: text + selector: 1 +``` diff --git a/docs/content/definition-attributes/selector.md b/docs/content/definition-attributes/selector.md new file mode 100644 index 0000000..20ba9b9 --- /dev/null +++ b/docs/content/definition-attributes/selector.md @@ -0,0 +1,47 @@ +# selector + +Selectors make it possible to "pick" a value from the parsed content. + +Structured content formats (ini, yaml, json) support selectors using - + - slash-notation (eg: `/foo/bar`) + - dot-notation (eg: `foo.0.bar`) + - brace-notation (eg: `foo[0]bar`) + +Text files support line-number selectors only; by default selector value is one (`1`) for text +content type, thus if omitted the first line only will be selected. + +NB: previous versions of env-alias supported a none/null selector that worked in teh same way as a none/null name, this +has been dropped in favour of a name-is-null only mechanism for such functionality. + + +### Example - dot-notation +Make a selection from a structured data source using dot-notation + +```yaml +env-alias: + EXAMPLE: + source: "https://ip-ranges.amazonaws.com/ip-ranges.json" + selector: ".prefixes[1].ip_prefix" +``` + + +### Example - slash-notation +Make a selection from a structured data source using slash-notation + +```yaml +env-alias: + EXAMPLE: + source: "https://ip-ranges.amazonaws.com/ip-ranges.json" + selector: "/prefixes[1]/ip_prefix" +``` + + +### Example - line-number +Make a selection from a flat text file by line number only + +```yaml +env-alias: + EXAMPLE: + source: "/proc/cpuinfo" + selector: 5 +``` diff --git a/docs/content/definition-attributes/source.md b/docs/content/definition-attributes/source.md new file mode 100644 index 0000000..1e8703f --- /dev/null +++ b/docs/content/definition-attributes/source.md @@ -0,0 +1,80 @@ +# source + +All Env Alias definitions should have a `source`, `exec` or `value` that sets the source content that +is subsequently passed to a parser. + +Four types of sources are available - + + * **source** (local) - any local file. + * **source** (remote) - any http-remote object available via a GET request. + * **exec** (stdout) - the STDOUT content from a shell-exec command. + * **value** (direct setting) - direct assignment of the value. + +It is possible to reference other environment-variables within a definition by using its name prefixed with +an `env:` string. + + +### Example - simple +Source content from line 5 in source file `/proc/cpuinfo` and assign to env-variable `EXAMPLE` +```yaml +env-alias: + EXAMPLE: + source: "/proc/cpuinfo" + selector: 5 +``` + + +### Example - getpass +Source content from the user using Python [getpass](https://docs.python.org/3/library/getpass.html) that is part +of the Python standard libraries. The Python getpass module ensures input is not echoed to terminal +```yaml +env-alias: + EXAMPLE: + source: "" +``` + + +### Example - stdin + +Source content from STDIN, that will be observable in the terminal output. +```yaml +env-alias: + EXAMPLE: + source: "" +``` + + +### Example - home path + +Source content from a file in the user home-path using tilde (`~`) notation. +```yaml +env-alias: + AWS_ACCESS_KEY_ID: + source: "~/.aws/credentials" + parser: ini + selector: "profile_name.aws_access_key_id" +``` + + +### Example - http remote + +Source content from a remote HTTP source +```yaml +env-alias: + EXAMPLE: + source: "https://ip-ranges.amazonaws.com/ip-ranges.json" + selector: "prefixes.2.ip_prefix" +``` + + +### Example - env reference + +Source location set by the value of another environment variable `EXAMPLE_SOURCE`. +```yaml +env-alias: + EXAMPLE_SOURCE: + value: "/proc/cpuinfo" + EXAMPLE: + source: "env:EXAMPLE_SOURCE" + selector: 5 +``` diff --git a/docs/content/definition-attributes/value.md b/docs/content/definition-attributes/value.md new file mode 100644 index 0000000..95566b3 --- /dev/null +++ b/docs/content/definition-attributes/value.md @@ -0,0 +1,29 @@ +# value + +The `value` definition-attribute makes it possible to directly assign a value to an environment variable, it is +the most straight forward use case. + +Additionally, it is possible to reference other environment variables via the `env:` prefix as shown. This can +be helpful when the value needs to be dynamic and used in subsequent definition steps. + +### Example - simple direct + +```yaml +env-alias: + + MYPROJECT_ENVVAR: + value: 'hello world' +``` + + +### Example - by reference + +```yaml +env-alias: + + MYPROJECT_ENVVAR: + value: 'hello world' + + MYPROJECT_REFERENCED_ENVVAR: + value: 'env:MYPROJECT_ENVVAR' +``` diff --git a/docs/content/development.md b/docs/content/development.md new file mode 100644 index 0000000..cd08bc3 --- /dev/null +++ b/docs/content/development.md @@ -0,0 +1,34 @@ +# Development + +This project uses the very awesome [slap-cli](https://niklasrosenstein.github.io/slap/) utility to help with packaging and release management. + +## slap-cli +```shell +# Create a new venv "env-alias" to work within +slap venv -cg env-alias + +# Activate the "env-alias" venv +slap venv -ag env-alias + +# Install the requirements for the "env-alias" development venv +slap install --upgrade --link + +# Update code formatting +slap run format + +# Test the package (pytest, black, isort, flake8, safety) +slap test + +# Write a "feature" changelog entry +slap changelog add -t "feature" -d "" [--issue ] + +# Bump the package version at the "patch" semver level +slap release patch --dry +slap release patch --tag [--push] + +# Build a package +slap publish --build-directory build --dry + +# Publish a package +slap publish +``` diff --git a/docs/content/examples/ansible-project.md b/docs/content/examples/ansible-project.md new file mode 100644 index 0000000..0011904 --- /dev/null +++ b/docs/content/examples/ansible-project.md @@ -0,0 +1,31 @@ + +# Ansible Project + +An example env-alias definition file for an Ansible project, this example does a few neat things - + + * Sets up the `ANSIBLE_VAULT_PASSWORD_FILE` without any additional setup + * Sets the `ANSIBLE_SSH_PIPELINING` to reduce latency between Ansible calls, thus speeding things up. + * Creates a throw-away SSH keypair that can be used to bootstrap a target instance + +```yaml +env-alias: + + MYPROJECT_ANSIBLE_VAULT_PASSWORD: + name: null # prevents this value being assigned into env + source: '~/secure/ansible-project/vault-pass.txt' + + ANSIBLE_VAULT_PASSWORD_FILE: + ansible_vault_password: "env:MYPROJECT_ANSIBLE_VAULT_PASSWORD" # NB: see docs how this gets managed + ansible_vault_password_file: true # invoke special helper that renders an Ansible Vault password file + + ANSIBLE_SSH_PIPELINING: + value: '1' + + MYPROJECT_BOOTSTRAP_SSH_KEYGEN: + exec: 'rm -f ~/secure/tmp/init-deployment.key; ssh-keygen -N "" -f ~/secure/tmp/init-deployment.key 2>&1' + name: null + + MYPROJECT_BOOTSTRAP_SSH_KEYADD: + exec: 'ssh-add -q ~/secure/tmp/init-deployment.key 2>&1' + name: null +``` diff --git a/docs/content/examples/aws-credentials.md b/docs/content/examples/aws-credentials.md new file mode 100644 index 0000000..8d8475d --- /dev/null +++ b/docs/content/examples/aws-credentials.md @@ -0,0 +1,24 @@ + +# AWS Credentials + +An easy example to get AWS credentials loaded in as environment variables from a non-default AWS +credentials file location. + +```yaml +env-alias: + + AWS_ACCESS_KEY_ID: + source: '~/credentials/aws/account_xxx/credentials' + parser: 'ini' + selector: 'profile_name.aws_access_key_id' + + AWS_SECRET_ACCESS_KEY: + source: '~/credentials/aws/account_xxx/credentials' + parser: 'ini' + selector: 'profile_name.aws_secret_access_key' + + AWS_DEFAULT_REGION: + source: '~/.aws/config' + parser: 'ini' + selector: 'profile account_name.region' +``` diff --git a/docs/content/examples/debugging.md b/docs/content/examples/debugging.md new file mode 100644 index 0000000..e82ab59 --- /dev/null +++ b/docs/content/examples/debugging.md @@ -0,0 +1,22 @@ +# Debugging + +Debug output can be easily added to STDERR by adding an optional `--debug` argument to the +`env-alias` command as shown below + +```shell +source <(env-alias --debug ~/projects/awesome/env-awesome-vars.yml) +``` + +Which will provides debug output to STDERR similar as shown +```shell +❯ env-awesome-vars +2024-09-21T17:34:24+1000 | DEBUG | env-alias | EnvAliasGenerator.generate() +2024-09-21T17:34:24+1000 | DEBUG | env-alias | EnvAliasConfig.load_definitions(definitions_file='dev/env-foobar.yml') +2024-09-21T17:34:24+1000 | DEBUG | env-alias | Definitions loaded from definitions_file='dev/env-foobar.yml' +2024-09-21T17:34:24+1000 | DEBUG | env-alias | Loaded environment definition for 'EXAMPLE' +2024-09-21T17:34:24+1000 | DEBUG | env-alias | Total 1 definitions in definitions_file=PosixPath('dev/env-foobar.yml') +2024-09-21T17:34:24+1000 | DEBUG | env-alias | EnvAliasGenerator.get_definition_value(definition.name='EXAMPLE', ...) +2024-09-21T17:34:24+1000 | DEBUG | env-alias | EnvAliasContent.local(filename=/proc/cpuinfo) +2024-09-21T17:34:24+1000 | DEBUG | env-alias | EnvAliasContent.local(filename=/proc/cpuinfo) > content_type='text' +2024-09-21T17:34:24+1000 | DEBUG | env-alias | output=' export "EXAMPLE"="model name\t: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz"' +``` diff --git a/docs/content/examples/terraform-aws-project.md b/docs/content/examples/terraform-aws-project.md new file mode 100644 index 0000000..37d82ef --- /dev/null +++ b/docs/content/examples/terraform-aws-project.md @@ -0,0 +1,42 @@ +# Terraform AWS project + +## Example + +The example below shows an Env Alias definition for setting up a Terraform environment. Notice that +all parts of the environment are easily established by calling a single alias name and that no secret +values are contained within. + + * Environment variable `TF_VAR_aws_access_key_id` is set by reading the file `~/.aws/credentials` + and selecting the value from `account_name.aws_access_key_id` + * Environment variable `TF_VAR_aws_secret_access_key` is set in a similar manner. + * The path `~/.terraform.d/plugin-cache` is created and the shell exec stdout is discarded. + * Environment variable `TF_PLUGIN_CACHE_DIR` is set directly in-line to the value `~/.terraform.d/plugin-cache` + +```yaml +env-alias: + + TF_PLUGIN_CACHE_DIR_CREATE: + exec: 'mkdir -p ~/.terraform.d/plugin-cache' + name: none + + TF_PLUGIN_CACHE_DIR: + value: '~/.terraform.d/plugin-cache' + + TF_VAR_aws_access_key_id: + source: '~/.aws/credentials' + parser: 'ini' + selector: 'account_name.aws_access_key_id' + + TF_VAR_aws_secret_access_key: + source: '~/.aws/credentials' + parser: 'ini' + selector: 'account_name.aws_secret_access_key' + + TF_VAR_aws_default_region: + source: '~/.aws/config' + parser: 'ini' + selector: 'profile account_name.region' + + TF_VAR_aws_ssh_key_name: + value: 'username' +``` diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 0000000..5fb30cc --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,8 @@ + +--- +title: Home +--- + +# Env Alias documentation + +@cat ../../README.md :with slice_lines = "1:" diff --git a/docs/content/license.md b/docs/content/license.md new file mode 100644 index 0000000..f5b1cfa --- /dev/null +++ b/docs/content/license.md @@ -0,0 +1,11 @@ +# License + +## BSD 2 Clause +```text +@cat ../../LICENSE :with slice_lines = "1:" +``` + +## Copyright + - Copyright © 2020 [Nicholas de Jong](https://www.nicholasdejong.com) + +All rights reserved. \ No newline at end of file diff --git a/docs/content/project.md b/docs/content/project.md new file mode 100644 index 0000000..2d1a957 --- /dev/null +++ b/docs/content/project.md @@ -0,0 +1,21 @@ +# Project + +[![PyPi](https://img.shields.io/pypi/v/env-alias.svg)](https://pypi.python.org/pypi/env-alias/) +[![Python Versions](https://img.shields.io/pypi/pyversions/env-alias.svg)](https://github.com/ndejong/env-alias/) +[![Read the Docs](https://img.shields.io/readthedocs/env-alias)](https://env-alias.readthedocs.io) +![License](https://img.shields.io/github/license/ndejong/env-alias.svg) + +## Project +* Github - [github.com/ndejong/env-alias](https://github.com/ndejong/env-alias) +* PyPI - [pypi.python.org/pypi/env-alias](https://pypi.python.org/pypi/env-alias/) +* ReadTheDocs - [env-alias.readthedocs.io](https://env-alias.readthedocs.io) + +## Features / Bugs +Please submit all feature-requests and bug-reports via Github issues + +* [https://github.com/ndejong/env-alias/issues](https://github.com/ndejong/env-alias/issues) + + +## Contact +* Nicholas de Jong +* [https://www.nicholasdejong.com](https://www.nicholasdejong.com) diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index e74a16b..0000000 --- a/docs/development.md +++ /dev/null @@ -1,43 +0,0 @@ -# Env Alias - -## Development -The following development tools are used to help create and manage this project. - -### shut -[shut](https://pypi.org/project/shut) is a Python package management and release -tool - [documentation link](https://github.com/NiklasRosenstein/shut/blob/develop/docs/docs/index.md) -```shell script -# Update package files -$ shut pkg update - -# Test the package -$ shut pkg test - -# Create a staged changelog entry for a fix/feature -$ shut changelog --add fix --stage --message "Fixes bug" -$ shut changelog --add feature --stage --message "Initial version" - -# Release bumps at patch/minor/major levels with --dry runs -$ shut pkg bump --patch --tag --push --dry -$ shut pkg bump --minor --tag --push --dry -$ shut pkg bump --major --tag --push --dry - -# Build a package -$ shut pkg build -vvv setuptools:wheel -$ shut pkg build -vvv setuptools:* - -# Publish a package -$ shut pkg publish --test warehouse:pypi -$ shut pkg publish warehouse:pypi -``` - -### pydoc-markdown -[pydoc-markdown](https://pypi.org/project/pydoc-markdown) is a documentation generation -tool that works well with Python modules - [documentation link](https://pydoc-markdown.readthedocs.io/en/latest/) -```shell script -# Render documentation -$ pydoc-markdown docs/pydoc-markdown.yml - -# Provide a local live review server -$ pydoc-markdown --server docs/pydoc-markdown.yml -``` diff --git a/docs/examples-debug.md b/docs/examples-debug.md deleted file mode 100644 index 9016266..0000000 --- a/docs/examples-debug.md +++ /dev/null @@ -1,18 +0,0 @@ -# Env Alias - -## Debug Output -Debug output can be easily added to STDERR by adding an optional `-d` argument to the -`env-alias` command as shown below - -```shell -eval $(env-alias my-alias-name -d ~/path-to/my-alias-name-config-file.yml) -``` - -Which will provides debug output to STDERR similar as shown -```shell -username@computer:~$ my-alias-name -20191201Z072045 - DEBUG - env-alias v0.0.1 -20191201Z072045 - DEBUG - export "local_text_01"="xxxxxxxxxxxxxxxx" -20191201Z072045 - DEBUG - export "local_text_02"="xxxxxxxxxxxxxxxx" -... -``` diff --git a/docs/examples-larger.md b/docs/examples-larger.md deleted file mode 100644 index 857bf99..0000000 --- a/docs/examples-larger.md +++ /dev/null @@ -1,95 +0,0 @@ -# Env Alias - -## Larger examples -The following larger examples show what can be achieved when putting sets of Env Alias -configurations together. - -### Ansible project setup -```yaml -env-alias: - - ANSIBLE_VAULT_PASSWORD: - source: '~/secure/ansible-project/vault-pass.txt' - - ANSIBLE_VAULT_PASSWORD_FILE: - value: '/tmp/.vault_echo_password' - - ANSIBLE_VAULT_PASSWORD_ECHO_FILE: - exec: 'echo "#!/bin/sh" > /tmp/.vault_echo_password; echo "echo \${ANSIBLE_VAULT_PASSWORD}" >> /tmp/.vault_echo_password; chmod 700 /tmp/.vault_echo_password' - selector: null - - ANSIBLE_SSH_PIPELINING: - value: '1' - - ANSIBLE_SSH_KEYGEN: - exec: 'rm -f ~/secure/tmp/init-deployment.key; ssh-keygen -N "" -f ~/secure/tmp/init-deployment.key 2>&1' - selector: null - - ANSIBLE_SSH_ADD: - exec: 'ssh-add -q ~/secure/tmp/init-deployment.key 2>&1' - selector: null -``` - -### AWS project setup -```yaml -env-alias: - - AWS_ACCESS_KEY_ID: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_access_key_id' - - AWS_SECRET_ACCESS_KEY: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_secret_access_key' - - AWS_DEFAULT_REGION: - source: '~/.aws/config' - parser: 'ini' - selector: 'profile account_name.region' -``` - -### Terraform - GCP project setup -```yaml -env-alias: - - CLOUDSDK_PYTHON: - value: 'python3' - - TF_VAR_gcloud_keyfile_json: - source: '~/secure/gcloud-account/service-account-1234567890.json' - - TF_VAR_gcloud_impersonated_user_email: - value: 'admin@foobar.com' -``` - -### Terraform - AWS project setup -```yaml -env-alias: - - TF_PLUGIN_CACHE_DIR_CREATE: - exec: 'mkdir -p ~/.terraform.d/plugin-cache' - selector: null - - TF_PLUGIN_CACHE_DIR: - value: '~/.terraform.d/plugin-cache' - - TF_VAR_aws_access_key_id: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_access_key_id' - - TF_VAR_aws_secret_access_key: - source: '~/.aws/credentials' - parser: 'ini' - selector: 'account_name.aws_secret_access_key' - - TF_VAR_aws_default_region: - source: '~/.aws/config' - parser: 'ini' - selector: 'profile account_name.region' - - TF_VAR_aws_ssh_key_name: - value: 'username' -``` \ No newline at end of file diff --git a/docs/examples-simple.md b/docs/examples-simple.md deleted file mode 100644 index 835adce..0000000 --- a/docs/examples-simple.md +++ /dev/null @@ -1,200 +0,0 @@ -# Env Alias - -## Simple examples -The following examples iterate through most of the possible permutations of source, parser -and selector. - -### local_text_01 -```yaml -env-alias: - local_text_01: - source: "/tmp/textfile.txt" -``` -Assign the env `local_text_01` to the value of the 1st line of text in `/tmp/textfile.txt` - -### local_text_02 -```yaml -env-alias: - local_text_02: - source: "/tmp/textfile.txt" - selector: 2 -``` -Assign the env `local_text_02` to the value of the 2nd line of text in `/tmp/textfile.txt` - -### local_text_03 -```yaml -env-alias: - local_text_03: - source: "/tmp/textfile_without_extension" -``` -Assign the env `local_text_03` using the 1st line of text in the text file; text file format used as default file format - -### local_text_04 -```yaml -env-alias: - local_text_04: - name: "local_text_04_override_name" - source: "/tmp/textfile.txt" -``` -Assign the env `local_text_04_override_name` using the 1st line of text in the file and use a different variable name - -### local_text_05 -```yaml -env-alias: - local_text_05: - source: "env:some_env_with_a_filename" -``` -Assign the env `local_text_05` using the 1st line of text in the file specified by env variable `${some_env_with_a_filename}` - -### local_text_06 -```yaml -env-alias: - local_text_06: - source: "/tmp/textfile.txt" - parser: "text" -``` -Assign the env `local_text_06` using the 1st line of text in the file and force the "text" content parser which is the default parser anyway - -### local_ini_01 -```yaml -env-alias: - local_ini_01: - source: "/tmp/inifile.ini" - selector: "foo.bar" -``` -Assign the env `local_ini_01` from the `[foo]` section under the `[bar]` option value; parser determined by filename extension - -### local_ini_02 -```yaml -env-alias: - local_ini_02: - source: "/tmp/file_without_ini_extension" - selector: "foo.bar" - parser: "ini" -``` -Assign the env `local_ini_02` from the `[foo]` section under the `[bar]` option value; parser manually set since it can not be determined via filename extension - -### local_json_01 -```yaml -env-alias: - local_json_01: - source: "/tmp/jsonfile.json" - selector: "foo.0.bar" -``` -Assign the env `local_json_01` from the JSON content using an xpath-style path selector to the desired value - -### local_json_02 -```yaml -env-alias: - local_json_02: - source: "/tmp/jsonfile.json" - selector: ".foo[1]bar" -``` -Assign the env `local_json_02` from the JSON content using a jq-style path selector to the desired value - -### local_json_03 -```yaml -env-alias: - local_json_03: - source: "/tmp/file_without_json_extension" - selector: "foo.0.bar" - parser: "json" -``` -Assign the env `local_json_03` from the JSON content using an xpath-style path selector to the desired value; set the JSON parser - -### local_yaml_01 -```yaml -env-alias: - local_yaml_01: - source: "/tmp/yamlfile.yml" - selector: "foo.0.bar" -``` -Assign the env `local_yaml_01` from the JSON content using an xpath-style path selector to the desired value; js-style is also possible here. - -### local_yaml_02 -```yaml -env-alias: - local_yaml_02: - source: "/tmp/file_without_yaml_extension" - selector: "foo.0.bar" - parser: "yaml" -``` -Assign the env `local_yaml_02` from the YAML content using an xpath-style path selector to the desired value; set the YAML parser - -### remote_text_01 -```yaml -env-alias: - remote_text_01: - source: "http://textfiles.com/computers/144disk.txt" -``` -Assign the env `remote_text_01` from the 1st line of the remote TEXT content - -### remote_json_01 -```yaml -env-alias: - remote_json_01: - source: "https://ip-ranges.amazonaws.com/ip-ranges.json" - selector: ".prefixes[2].ip_prefix" -``` -Assign the env `remote_json_01` from remote JSON content using a jq-style selector - -### remote_json_02 -```yaml -env-alias: - remote_json_02: - source: "https://ip-ranges.amazonaws.com/ip-ranges.json" - selector: "prefixes.2.ip_prefix" -``` -Assign the env `remote_json_02` from remote JSON content using an xpath-style selector - -### exec_01 -```yaml -env-alias: - exec_01: - exec: "head /dev/urandom | base64 -w0 | tr -d "/" | tr -d "+" | head -c20" -``` -Assign the env `exec_01` from the 1st line of the STDOUT of an shell command - -### exec_02 -```yaml -env-alias: - exec_02: - exec: "curl -s https://ip-ranges.amazonaws.com/ip-ranges.json" - parser: "json" - selector: ".prefixes[1].ip_prefix" -``` -Assign the env `exec_02` from the 1st line of the STDOUT of an shell command - -### exec_03 -```yaml -env-alias: - exec_03: - exec: "head /dev/urandom | base64 -w0" - selector: null -``` -Run the shell-command and do not assign it to any env value - -### direct_01 -```yaml -env-alias: - direct_01: - value: "somevalue" -``` -Assign env `direct_01` to value "somevalue" - -### direct_02 -```yaml -env-alias: - direct_02: - value: "env:HOME" -``` -Use an existing env value as input into this configuration; can be used in any env-alias option - -### direct_03 -```yaml -env-alias: - direct_03: - name: "direct_03_override_name" - value: "env:HOME" -``` -Set env set and override the variable name; can be used in any env-alias setting arrangement diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..9e7d651 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,30 @@ + +docs_dir: content +site_name: "Env Alias" + +nav: + - Home: index.md + + - Definition Attributes: + - definition-attributes/overview.md + - definition-attributes/source.md + - definition-attributes/parser.md + - definition-attributes/selector.md + - definition-attributes/name.md + - definition-attributes/value.md + - definition-attributes/exec.md + - definition-attributes/override.md + - definition-attributes/keepass-password.md + - definition-attributes/ansible-vault-password.md + - definition-attributes/ansible-vault-password-file.md + + - Examples: + - examples/terraform-aws-project.md + - examples/aws-credentials.md + - examples/ansible-project.md + - examples/debugging.md + + - Other: + - project.md + - development.md + - license.md diff --git a/docs/project.md b/docs/project.md deleted file mode 100644 index e61f8cd..0000000 --- a/docs/project.md +++ /dev/null @@ -1,36 +0,0 @@ -# Env Alias - -[![PyPi](https://img.shields.io/pypi/v/env-alias.svg)](https://pypi.python.org/pypi/env-alias/) -[![Python Versions](https://img.shields.io/pypi/pyversions/env-alias.svg)](https://github.com/ndejong/env-alias/) -[![Build Status](https://github.com/ndejong/env-alias/actions/workflows/build-tests.yml/badge.svg)](https://github.com/ndejong/env-alias/actions/workflows/build-tests.yml) -[![Read the Docs](https://img.shields.io/readthedocs/env-alias)](https://env-alias.readthedocs.io) -![License](https://img.shields.io/github/license/ndejong/env-alias.svg) - -## Project -* Github - [github.com/ndejong/env-alias](https://github.com/ndejong/env-alias) -* PyPI - [pypi.python.org/pypi/env-alias](https://pypi.python.org/pypi/env-alias/) -* TravisCI - [travis-ci.org/github/ndejong/env-alias](https://travis-ci.org/github/ndejong/env-alias) -* ReadTheDocs - [env-alias.readthedocs.io](https://env-alias.readthedocs.io) - -## Features / Bugs -Please submit all feature-requests and bug-reports via Github issues - -* https://github.com/ndejong/env-alias/issues - -## Contact -* Nicholas de Jong -* https://nicholasdejong.com - -## Other projects same author -* [Arpwitch](https://arpwitch.readthedocs.io) - A modern arpwatch replacement with JSON formatted outputs and easy options to exec commands (eg. `nmap`) when network changes are observed. -* [Elasticsearch Kibana CLI](https://elasticsearch-kibana-cli.readthedocs.io/) - Shell interface to query an ElasticSearch backend via the Kibana frontend which is useful in situations where the ElasticSearch backend is not otherwise accessible. -* [Env Alias](https://env-alias.readthedocs.io) - Helper utility to create shell alias commands to easily set collections of environment variables often with secret values from a variety of data-sources and data-formats. -* [Phishing Tracker](https://github.com/ndejong/phishing-tracker) - Utility to manage sets of phishing links making it easier to track their removal progress over time. -* [PyVBoxManage](https://pyvboxmanage.readthedocs.io) - Wrapper tool for VBoxManage that orchestrates commands using a YAML configuration file. -* [Terraform + Digital Ocean Droplets](https://registry.terraform.io/modules/verbnetworks/droplet/digitalocean/latest) - Terraform module to create a Digital Ocean Droplet using Terraform with desirable additional features. - -* [pfSense FauxAPI](https://github.com/ndejong/pfsense_fauxapi) - A REST API interface for pfSense 2.3.x, 2.4.x, 2.5.x to facilitate devops. -* [FauxAPI Python module](https://github.com/ndejong/pfsense_fauxapi_client_python) - Python client for pfSense FauxAPI. - -* [Digital Multimeter](https://digital-multimeter.readthedocs.io) - Digital Multimeter provides both a command-line interface and a Python module interface to receive data from a variety of digital multimeters. -* [SolarEdge Interface](https://solaredge-interface.readthedocs.io) - The SolarEdge Interface provides both a command-line interface and a Python module interface to interact with the SolarEdge API service. -* [Philippines Data Mashup](https://github.com/ndejong/philippines-data) - Mashup of data from the Philippine Statistics Authority (PSA) and Google Maps; see the [heatmap](https://nicholasdejong.com/projects/philippines-data/heatmap/) diff --git a/docs/pydoc-markdown.yml b/docs/pydoc-markdown.yml deleted file mode 100644 index 9afa634..0000000 --- a/docs/pydoc-markdown.yml +++ /dev/null @@ -1,118 +0,0 @@ - -#@ def base_url(): -#@ if env.READTHEDOCS: -#@ return "https://env-alias.readthedocs.io/en/" + env.READTHEDOCS_VERSION + "/" -#@ else: -#@ return None -#@ end - -loaders: - - type: python - search_path: [../src] - -#hooks: -# pre-render: -# - shut --cwd .. changelog -a --markdown > ../CHANGELOG.md - -processors: - - type: crossref - - type: filter - exclude_special: true - documented_only: true - exclude_private: true - -renderer: - type: hugo - - markdown: - toc_maxdepth: 3 - classdef_code_block: true - descriptive_class_title: false - signature_with_def: true - signature_with_decorators: false - signature_code_block: true - render_module_header: true - header_level_by_type: - Method: 3 - Function: 3 - source_linker: - type: github - repo: ndejong/env-alias - source_format: '###### *[[view source]]({url})*' - - config: - baseURL: #@ base_url() - title: Env Alias - theme: {clone_url: "https://github.com/alex-shpak/hugo-book.git"} - - build_directory: docs/build - content_directory: content/docs # The "alex-shpak/hugo-book" theme only renders pages in "content/docs" into the nav. - default_preamble: {menu: main} - - pages: - - - title: Home - preamble: - weight: 10 - name: index - source: ../README.md - directory: '..' - - - title: Configuration - preamble: - weight: 20 - source: configuration.md - children: - - title: Sources - preamble: - weight: 21 - source: configuration-sources.md - - title: Parsers - preamble: - weight: 22 - source: configuration-parsers.md - - title: Selectors - preamble: - weight: 23 - source: configuration-selectors.md - - - title: Examples - preamble: - weight: 30 - children: - - title: Simple - preamble: - weight: 31 - source: examples-simple.md - - title: Larger - preamble: - weight: 32 - source: examples-larger.md - - title: Debug - preamble: - weight: 33 - source: examples-debug.md - - - title: Project - preamble: - weight: 40 - name: project - source: project.md - -# - title: Changelog -# preamble: -# weight: 50 -# name: changelog -# source: ../CHANGELOG.md - - - title: Development - preamble: - weight: 60 - name: development - source: development.md - - - title: License - preamble: - weight: 70 - name: license - source: ../LICENSE diff --git a/package.yml b/package.yml deleted file mode 100644 index 6f3df95..0000000 --- a/package.yml +++ /dev/null @@ -1,45 +0,0 @@ - -name: env-alias -version: 0.4.0 -author: Nicholas de Jong -license: BSD2 -description: Powerful helper utility to create shell alias commands to easily set collections of environment variables often with secret values from a variety of data-sources and data-formats. -url: https://env-alias.readthedocs.io/ - -typed: false - -entrypoints: - console_scripts: - - env-alias = env_alias.cli.entrypoints:env_alias - - env-alias-generator = env_alias.cli.entrypoints:env_alias_generator - -test-drivers: - - type: pytest - parallelism: 8 - - type: pylint - args: [ '--fail-under=9.0', '--ignore=vendor', '--ignore=tests', '--indent-string=" "', '--disable=W1202,W1203,C0209' ] - -templates: - - type: pylintrc - use: shut - -requirements: - - python ^3.6 - - pyyaml - -classifiers: - - "Environment :: Console" - - "Intended Audience :: Developers" - - "Intended Audience :: Information Technology" - - "Programming Language :: Python :: 3.6" - - "Programming Language :: Python :: 3.7" - - "Programming Language :: Python :: 3.8" - - "Programming Language :: Python :: 3.9" - - "License :: OSI Approved :: BSD License" - -keywords: - - 'env-alias' - - 'shell' - - 'env' - - 'alias' - - 'bash' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5f7ce1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "env-alias" +version = "0.5.1" +description = "Powerful helper utility to create shell alias commands to easily set collections of environment variables often with secret values from a variety of data-sources and data-formats." +authors = ["Nicholas de Jong "] +license = "BSD-2-Clause" +readme = "README.md" +packages = [{ include = "env_alias", from = "src" }] +classifiers = [ + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] +keywords = ["env-alias", "shell", "env", "alias", "bash"] + + +[tool.poetry.urls] +Documentation = "https://env-alias.readthedocs.io/" +Homepage = "https://github.com/ndejong/env-alias" +Repository = "https://github.com/ndejong/env-alias" +"Bug Tracker" = "https://github.com/ndejong/env-alias/issues" + +[tool.poetry.scripts] +env-alias = "env_alias.main:entrypoint" + + +[tool.poetry.dependencies] +python = "^3.8" +pyyaml = ">=5.0" # https://pypi.org/project/pyyaml/#history + +[tool.poetry.dev-dependencies] +black = ">=23.0" # https://pypi.org/project/black/#history +flake8 = ">=7.1" # https://pypi.org/project/flake8/#history +isort = ">=5.13" # https://pypi.org/project/isort/#history +mypy = ">=1.10" # https://pypi.org/project/mypy/#history +pycln = ">=2.4" # https://pypi.org/project/pycln/#history +pytest = ">=8.2" # https://pypi.org/project/pytest/#history +safety = ">=3.2" # https://pypi.org/project/safety/#history +types-aiofiles = ">=23.2" # https://pypi.org/project/types-aiofiles/#history +types-PyYAML = ">=6.0" # https://pypi.org/project/types-PyYAML/#history +# NB: pip installs in .readthedocs.yml need to be kept up-to-date manually +novella = ">=0.2" # https://pypi.org/project/novella/#history +pydoc-markdown = ">=4.8" # https://pypi.org/project/pydoc-markdown/#history +mkdocs-material = ">=9.5" # https://pypi.org/project/mkdocs-material/#history + +[tool.poetry.plugins."slap.plugins.check"] +changelog = "slap.ext.checks.changelog:ChangelogValidationCheckPlugin" +general = "slap.ext.checks.general:GeneralChecksPlugin" +poetry = "slap.ext.checks.poetry:PoetryChecksPlugin" +release = "slap.ext.checks.release:ReleaseChecksPlugin" + +[tool.slap] +typed = true +release.branch = "dev" + +[tool.slap.test] +check = "slap check" +black = "black --check src/ tests/" +flake8 = "flake8 src/ tests/" +isort = "isort --check-only src/ tests/" +mypy = "dmypy run src/" +pycln = "pycln src/ tests/ --check" +safety = "pip freeze | safety check --stdin --short-report --output text" +pytest = "pytest tests/ -vv" + +[tool.slap.run] +format = "black src/ tests/ && isort src/ tests/" +docs-build = "cd docs && novella --base-url env-alias/" +docs-server = "cd docs && novella --serve" + +[tool.mypy] +explicit_package_bases = true +ignore_missing_imports = true +mypy_path = ["src"] +namespace_packages = true +pretty = true +python_version = "3.8" +show_error_codes = true +show_error_context = true +strict = true +warn_no_return = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_ignores = true + +[tool.isort] +profile = "black" +line_length = 120 +combine_as_imports = true + +[tool.black] +line-length = 120 diff --git a/setup.py b/setup.py deleted file mode 100644 index 7cd31c5..0000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -# This file was auto-generated by Shut. DO NOT EDIT -# For more information about Shut, check out https://pypi.org/project/shut/ - -from __future__ import print_function -import io -import os -import setuptools -import sys - -readme_file = 'README.md' -if os.path.isfile(readme_file): - with io.open(readme_file, encoding='utf8') as fp: - long_description = fp.read() -else: - print("warning: file \"{}\" does not exist.".format(readme_file), file=sys.stderr) - long_description = None - -requirements = [ - 'pyyaml', -] - -setuptools.setup( - name = 'env-alias', - version = '0.4.0', - author = 'Nicholas de Jong', - author_email = 'contact@nicholasdejong.com', - description = 'Powerful helper utility to create shell alias commands to easily set collections of environment variables often with secret values from a variety of data-sources and data-formats.', - long_description = long_description, - long_description_content_type = 'text/markdown', - url = 'https://env-alias.readthedocs.io/', - license = 'BSD2', - packages = setuptools.find_packages('src', ['test', 'test.*', 'tests', 'tests.*', 'docs', 'docs.*']), - package_dir = {'': 'src'}, - include_package_data = True, - install_requires = requirements, - extras_require = {}, - tests_require = [], - python_requires = '>=3.6.0,<4.0.0', - data_files = [], - entry_points = { - 'console_scripts': [ - 'env-alias = env_alias.cli.entrypoints:env_alias', - 'env-alias-generator = env_alias.cli.entrypoints:env_alias_generator', - ] - }, - cmdclass = {}, - keywords = ['env-alias', 'shell', 'env', 'alias', 'bash'], - classifiers = ['Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'License :: OSI Approved :: BSD License'], - zip_safe = True, -) diff --git a/src/bin/env-alias b/src/bin/env-alias deleted file mode 100755 index 68b1c17..0000000 --- a/src/bin/env-alias +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import re -import sys - -try: - from env_alias.cli import entrypoints -except ModuleNotFoundError as e: - sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - from env_alias.cli import entrypoints - -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(entrypoints.env_alias()) diff --git a/src/bin/env-alias-generator b/src/bin/env-alias-generator deleted file mode 100755 index 1cab2b7..0000000 --- a/src/bin/env-alias-generator +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import re -import sys - -try: - from env_alias.cli import entrypoints -except ModuleNotFoundError as e: - sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - from env_alias.cli import entrypoints - -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(entrypoints.env_alias_generator()) diff --git a/src/env_alias/__init__.py b/src/env_alias/__init__.py index 53cd1f7..f582334 100644 --- a/src/env_alias/__init__.py +++ b/src/env_alias/__init__.py @@ -1,10 +1,16 @@ -# -*- coding: utf8 -*- -# Copyright (c) 2020 Nicholas de Jong +# +# Copyright [2020] Nicholas de Jong (https://www.nicholasdejong.com) +# -__title__ = "env-alias" -__author__ = "Nicholas de Jong " -__version__ = "0.4.0" -__license__ = "BSD2" +from os import getenv +from sys import argv -__logger_default_level__ = "error" -__env_alias_generator__ = "env-alias-generator" +__title__ = "Env Alias" +__version__ = "0.5.1" + +LOGGER_LEVEL = "info" +if "--debug" in argv or getenv("ENVALIAS_DEBUG", "").lower().startswith(("true", "yes", "enable", "on")): + LOGGER_LEVEL = "debug" + +LOGGER_NAME = "env-alias" +DEFINITIONS_ROOT = "env-alias" diff --git a/src/env_alias/cli/entrypoints.py b/src/env_alias/cli/entrypoints.py deleted file mode 100644 index 6f977d7..0000000 --- a/src/env_alias/cli/entrypoints.py +++ /dev/null @@ -1,72 +0,0 @@ -import sys -import argparse - -from env_alias import __title__ as NAME -from env_alias import __version__ as VERSION -from env_alias import __logger_default_level__ as LOGGER_DEFAULT_LEVEL -from env_alias.exceptions import EnvAliasException - - -def env_alias(): - from env_alias.env_alias import EnvAlias - - try: - EnvAlias().main() - except EnvAliasException as e: - __entrypoint_exception_handler(e) - - -def env_alias_generator(): - from env_alias.env_alias_generator import EnvAliasGenerator - - parser = argparse.ArgumentParser( - description="{} v{}".format(NAME, VERSION), - add_help=False, - epilog=""" - Helper tool to create shell alias commands to easily set collections of environment variables, often with - secrets, from a variety of sources and formats. Typically this tool is invoked via an entry in - `.bash_aliases` with an entry in the form - `eval $(env-alias my-alias-name ~/path-to/my-alias-name-config-file.yml)` where this example would hence - establish a shell alias for the command `my-alias-name` that then invokes this `env-alias-generator` with - the configuration from `~/path-to/my-alias-name-config-file.yml`. The result then is that the environment - variables defined in the configuration are loaded into the current shell. This provides an easy mechanism - to manage sets of environment variables with values from encrypted or otherwise secured data-sources - through one simple alias command and a configuration file that can safely be committed to source control - without exposing secret values. - """, - ) - - parser.add_argument( - "config", metavar="", type=str, nargs=1, help="An env-alias YAML style configuration file." - ) - - parser.add_argument( - "-d", "--debug", action="store_true", default=False, help="Debug logging output (default: False)." - ) - - if len(sys.argv) == 1: - parser.print_help() - print() - exit(1) - - args = parser.parse_args() - - if args.debug: - logger_level = "debug" - else: - logger_level = LOGGER_DEFAULT_LEVEL - - try: - EnvAliasGenerator(logger_level=logger_level).main(configuration_file=args.config) - except EnvAliasException as e: - __entrypoint_exception_handler(e) - - -def __entrypoint_exception_handler(e): - print("") - print("{} v{}".format(NAME, VERSION)) - print("ERROR: ", end="") - for err in iter(e.args): - print(err) - print("") - exit(9) diff --git a/src/env_alias/env_alias.py b/src/env_alias/env_alias.py deleted file mode 100755 index 043bce4..0000000 --- a/src/env_alias/env_alias.py +++ /dev/null @@ -1,15 +0,0 @@ -import sys -from env_alias import __title__ -from env_alias import __version__ -from env_alias import __env_alias_generator__ - - -class EnvAlias: - def main(self): - args = sys.argv[1:] - if "--version" in args: - print(f"{__title__} v{__version__}") - elif len(args) < 2 or len(args) > 3: - print("Usage: env-alias [-d] ") - else: - print('alias "{}"="source <({} {})"'.format(args[0], __env_alias_generator__, " ".join(args[1:]))) diff --git a/src/env_alias/env_alias_generator.py b/src/env_alias/env_alias_generator.py deleted file mode 100644 index b9cd85e..0000000 --- a/src/env_alias/env_alias_generator.py +++ /dev/null @@ -1,75 +0,0 @@ -from env_alias import __title__ -from env_alias import __version__ - -from env_alias.utils.logger import Logger -from env_alias.utils.config import EnvAliasConfig -from env_alias.utils.content import EnvAliasContent -from env_alias.utils.selector import EnvAliasSelector -from env_alias.exceptions import EnvAliasException - - -logger = Logger(name=__title__).logging - - -class EnvAliasGenerator: - def __init__(self, logger_level="warning"): - Logger(name=__title__).setup(level=logger_level) - logger.info(f"{__title__} v{__version__}") - - def main(self, configuration_file=None, no_space=False) -> None: - - if isinstance(configuration_file, list): - configuration_file = configuration_file[0] - - config = EnvAliasConfig(config_root=__title__, configuration_file=configuration_file).config - - if not config: - raise EnvAliasException("Empty configuration provided") - - for config_k, config_v in config.items(): - env_name = config_k - if "name" in config_v.keys(): - env_name = config_v["name"] - output_prefix = " " # prevents shell command history - if no_space is True: - output_prefix = "" - - setting_value = self.get_setting(config_k, config_v) - if setting_value is not None: - output = '{}export "{}"="{}"'.format(output_prefix, env_name, setting_value) - logger.debug(output) - print(output) - - def get_setting(self, config_key, config): - logger.debug(f"{__name__} get_setting(config_key={config_key} )") - - if "value" in config.keys(): - return config["value"] - elif "source" in config.keys() and config["source"][0:4] == "http": - content, content_type = EnvAliasContent.remote(config["source"]) - elif "source" in config.keys(): - content, content_type = EnvAliasContent.local(config["source"]) - elif "exec" in config.keys(): - content, content_type = EnvAliasContent.execute(config["exec"]) - else: - raise EnvAliasException('Configuration of env-alias item "{}" is malformed'.format(config_key)) - - parser = content_type - if "parser" in config.keys(): - parser = config["parser"].lower() - - selector = None - if "selector" in config.keys(): - if config["selector"] is None or config["selector"] == "null": - selector = "none" # edge case where "selector" exists and set to none (or null) - else: - selector = config["selector"] - - if parser == "ini": - return EnvAliasSelector.ini_content(content, selector) - elif parser == "json": - return EnvAliasSelector.json_content(content, selector) - elif parser in ["yaml", "yml"]: - return EnvAliasSelector.yaml_content(content, selector) - - return EnvAliasSelector.text_content(content, selector) diff --git a/src/env_alias/exceptions.py b/src/env_alias/exceptions.py new file mode 100644 index 0000000..89d51ed --- /dev/null +++ b/src/env_alias/exceptions.py @@ -0,0 +1,20 @@ +from typing import Any, List, Union + +from . import LOGGER_LEVEL, LOGGER_NAME +from .lib.logger import logger_get + +logger = logger_get(name=LOGGER_NAME, loglevel=LOGGER_LEVEL) + + +class EnvAliasBaseException(Exception): + def __init__(self, *args: Union[str, List[Any]], **kwargs: Any) -> None: + log_message = " ".join([str(x) for x in args]).strip() + if log_message: + logger.error(f"{log_message}") + if "detail" in kwargs and LOGGER_LEVEL == "debug": + logger.error(f"{kwargs['detail']}".strip()) + super().__init__(*args) + + +class EnvAliasException(EnvAliasBaseException): + pass diff --git a/src/env_alias/exceptions/__init__.py b/src/env_alias/exceptions/__init__.py deleted file mode 100644 index 05b0448..0000000 --- a/src/env_alias/exceptions/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -class EnvAliasException(Exception): - pass diff --git a/src/env_alias/cli/__init__.py b/src/env_alias/lib/__init__.py similarity index 100% rename from src/env_alias/cli/__init__.py rename to src/env_alias/lib/__init__.py diff --git a/src/env_alias/lib/definitions.py b/src/env_alias/lib/definitions.py new file mode 100644 index 0000000..09fc729 --- /dev/null +++ b/src/env_alias/lib/definitions.py @@ -0,0 +1,61 @@ +import os +from pathlib import Path +from typing import List, Optional + +import yaml + +from .. import DEFINITIONS_ROOT, LOGGER_LEVEL, LOGGER_NAME +from ..exceptions import EnvAliasException +from ..lib.logger import logger_get +from ..models.envalias_definition import EnvAliasDefinition + +logger = logger_get(name=LOGGER_NAME, loglevel=LOGGER_LEVEL) + + +class EnvAliasDefinitions: + definitions_root: str = DEFINITIONS_ROOT + + def __init__(self, definitions_root: Optional[str] = None): + if definitions_root: + self.definitions_root = definitions_root + + def load_definitions(self, definitions_file: Path) -> List[EnvAliasDefinition]: + logger.debug(f"EnvAliasConfig.load_definitions(definitions_file={str(definitions_file)!r})") + + if not os.path.isfile(definitions_file): + raise EnvAliasException(f"Unable to locate definitions_file={str(definitions_file)!r}") + + loaded_definitions = {} + + with open(definitions_file, "r") as f: + try: + loaded_definitions = yaml.safe_load(f.read()) + except yaml.YAMLError as e: + raise EnvAliasException(f"Failed to load definitions_file={str(definitions_file)!r}", detail=e) + + if not isinstance(loaded_definitions, dict) or self.definitions_root not in loaded_definitions.keys(): + raise EnvAliasException(f"Unable to locate top-level definitions root {self.definitions_root!r}") + logger.debug(f"Definitions loaded from definitions_file={str(definitions_file)!r}") + + definitions: List[EnvAliasDefinition] = [] + for definition_key, definition_item in loaded_definitions[self.definitions_root].items(): + if not isinstance(definition_item, dict): + raise EnvAliasException(f"Definition item {definition_key!r} is not dict type") + + definition_item["_filename"] = definitions_file + + if "name" in definition_item.keys() and definition_item["name"] is None: + definition_item["_is_internal_only"] = True + + if not definition_item.get("name"): + definition_item["name"] = definition_key + + try: + definitions.append(EnvAliasDefinition(**definition_item)) + except TypeError as e: + raise EnvAliasException(str(e)) + + logger.debug(f"Loaded environment definition for {definition_item['name']!r}") + + logger.debug(f"Total {len(definitions)} definitions in {definitions_file=}") + return definitions diff --git a/src/env_alias/lib/generator.py b/src/env_alias/lib/generator.py new file mode 100644 index 0000000..d646da5 --- /dev/null +++ b/src/env_alias/lib/generator.py @@ -0,0 +1,146 @@ +import os +import sys +from pathlib import Path +from typing import Dict, Union + +from .. import DEFINITIONS_ROOT, LOGGER_LEVEL, LOGGER_NAME +from ..exceptions import EnvAliasException +from ..lib.definitions import EnvAliasDefinitions +from ..lib.logger import logger_get +from ..lib.selector import EnvAliasSelector +from ..lib.source import EnvAliasSource +from ..models.envalias_definition import EnvAliasDefinition +from ..models.sourced_content import SourcedContent + +logger = logger_get(name=LOGGER_NAME, loglevel=LOGGER_LEVEL) + + +class EnvAliasGenerator: + definitions_file: Path + values_generated: Dict[str, str] = {} + + def __init__(self, config_file: Path): + self.definitions_file = config_file + + def generate(self) -> None: + logger.debug("EnvAliasGenerator.generate()") + + definitions = EnvAliasDefinitions(definitions_root=DEFINITIONS_ROOT).load_definitions(self.definitions_file) + if not definitions: + raise EnvAliasException(f"Empty or malformed {self.definitions_file=}") + + for definition in definitions: + is_existing_setting = os.getenv(definition.name) or self.values_generated.get(definition.name) + if definition.override is False and is_existing_setting: + logger.debug(f"Skipping {definition.name!r} because already set and definition.override=False.") + continue + + value = self.get_definition_value(definition=self.update_env_replacement_attributes(definition)) + + if value is not None: # NB: not just "if value" because value could be a valid empty string + self.values_generated[definition.name] = value + if definition._is_internal_only is True: + logger.debug( + f"Definition for {definition.name!r} defines a 'null' name for env-alias internal " + f"only use, skipping generated output." + ) + continue + + self.output_export(env_name=definition.name, env_value=value) + + if definition.ansible_vault_password_file: # special additional output case + self.output_export( + env_name=self.values_generated[value], env_value=definition.ansible_vault_password + ) + + def output_export(self, env_name: str, env_value: Union[str, None] = "", output_prefix: str = " ") -> None: + output = f'{output_prefix}export "{env_name}"="{env_value}"' + logger.debug(f"output={output!r}") + print(output, file=sys.stdout) # NB: force stdout + + def get_definition_value(self, definition: EnvAliasDefinition) -> Union[str, None]: + logger.debug(f"EnvAliasGenerator.get_definition_value({definition.name=}, ...)") + + if definition.value: + return definition.value + + sourced_content = self.get_content_from_source(definition=definition) + + parser = sourced_content.content_type + if definition.parser: + parser = definition.parser + + if sourced_content.source_method in ("getpass", "stdin", "keepass") or parser == "none": + return sourced_content.content + + if definition.selector == "none": + return None + + if parser == "ini": + return EnvAliasSelector.ini_content(sourced_content.content, definition.selector) + elif parser == "json": + return EnvAliasSelector.json_content(sourced_content.content, definition.selector) + elif parser in ["yaml", "yml"]: + return EnvAliasSelector.yaml_content(sourced_content.content, definition.selector) + + if definition.selector and not str(definition.selector).isdigit(): + raise EnvAliasException(f"Selector for plaintext content must be number {definition.selector=}") + elif definition.selector is None: + definition.selector = "1" + + return EnvAliasSelector.text_content(sourced_content.content, selector=int(definition.selector)) + + def get_content_from_source(self, definition: EnvAliasDefinition) -> SourcedContent: + if definition.source and definition.source.startswith("http"): + sourced_content = EnvAliasSource.remote(url=definition.source) + + elif definition.source and definition.source == "": + sourced_content = EnvAliasSource.stdin(prompt=f"Enter {definition.name!r} value using : ") + + elif definition.source and definition.source == "": + sourced_content = EnvAliasSource.getpass(prompt=f"Enter {definition.name!r} value using : ") + + elif definition.source and definition.keepass_password: + sourced_content = EnvAliasSource.keepass( + filename=Path(definition.source), password=definition.keepass_password, selector=definition.selector + ) + + elif definition.source and definition.ansible_vault_password: + sourced_content = EnvAliasSource.ansible_vault( + filename=Path(definition.source), password=definition.ansible_vault_password + ) + + elif definition.ansible_vault_password and definition.ansible_vault_password_file: + sourced_content = EnvAliasSource.ansible_vault_password_file(password=definition.ansible_vault_password) + self.values_generated[sourced_content.content] = sourced_content.source # for the special additional output + + elif definition.source: + sourced_content = EnvAliasSource.local(filename=Path(definition.source)) + + elif definition.exec: + sourced_content = EnvAliasSource.execute(command_line=definition.exec) + + else: + raise EnvAliasException(f"Definition for env-alias {definition.name!r} is malformed.") + + return sourced_content + + def update_env_replacement_attributes(self, definition: EnvAliasDefinition) -> EnvAliasDefinition: + attr_names = ["value", "source", "parser", "selector", "keepass_password", "ansible_vault_password"] + for attr_name in attr_names: + attr_value = getattr(definition, attr_name) + if attr_value and str(attr_value).startswith("env:"): + setattr(definition, attr_name, self.get_replacement_env_value(attr_value)) + return definition + + def get_replacement_env_value(self, value: str) -> str: + env_name = value[4:] + + env_value = os.getenv(env_name, None) + if env_value is None or len(value) < 1: + env_value = self.values_generated.get(env_name) + + if env_value is None: + raise EnvAliasException(f"Definition replacement using {value!r} environment value that is unset.") + + return env_value diff --git a/src/env_alias/lib/logger.py b/src/env_alias/lib/logger.py new file mode 100644 index 0000000..4b84e27 --- /dev/null +++ b/src/env_alias/lib/logger.py @@ -0,0 +1,110 @@ +import logging +from typing import Any, Callable, Union + +LOGGING_FORMAT = "%(asctime)s | %(levelname)s | __name__ | %(message)s" +LOGGING_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S%z" + + +class LoggerNone: + def __getattr__(self, _: Any) -> Callable[..., Any]: + def empty(*_: Any) -> None: + pass + + return empty + + +def logger_get( + name: Union[str, None], loglevel: str = "warning", logfile: Union[str, None] = None +) -> Union[logging.Logger, LoggerNone]: + if name is None: + return LoggerNone() + + logger = logging.getLogger(name) + if logger.handlers: + return logger + + logging_level = __logger_level_int(loglevel) + logger.setLevel(logging_level) + + logging_formatter = LoggingFormatterWrapper( + fmt=LOGGING_FORMAT, datefmt=LOGGING_TIMESTAMP_FORMAT, name=name, colorize_levelname=True + ) + + console_handler = logging.StreamHandler() + console_handler.setLevel(logging_level) + console_handler.setFormatter(logging_formatter) + + logger.addHandler(console_handler) + + try: + if logfile: + file_handler = logging.FileHandler(filename=logfile) + file_handler.setLevel(logging_level) + file_handler.setFormatter(logging_formatter) + logger.addHandler(file_handler) + except (FileNotFoundError, PermissionError): + raise PermissionError(f"Unable to write to logfile at: {logfile}") + + return logger + + +def logger_setlevel(name: str, loglevel: str) -> Union[logging.Logger, LoggerNone]: + logger = logger_get(name) + logging_level = __logger_level_int(loglevel) + + logger.setLevel(logging_level) + if isinstance(logger.handlers, list): + for handler in logger.handlers: + handler.setLevel(logging_level) + + return logger + + +def __logger_level_int(loglevel: str) -> int: + logging_level = logging.getLevelName(loglevel.upper()) + try: + int(logging_level) + except ValueError: + raise ValueError(f"Unknown loglevel requested: {loglevel}") + + return int(logging_level) + + +class LoggingFormatterWrapper(logging.Formatter): + colorize_levelname: bool = False + + def __init__(self, **kwargs: Any) -> None: + if "name" in kwargs: + kwargs["fmt"] = kwargs.get("fmt", Any).replace("__name__", kwargs["name"]) + del kwargs["name"] + if "colorize_levelname" in kwargs: + self.colorize_levelname = True + del kwargs["colorize_levelname"] + logging.Formatter.__init__(self, **kwargs) + + def format(self, record: logging.LogRecord) -> str: + if self.colorize_levelname: + return self.colorized_levelname_format(record) + return logging.Formatter.format(self, record) + + def colorized_levelname_format(self, record: logging.LogRecord) -> str: + levelname = record.levelname.upper() + + if levelname == "CRITICAL": + color_code = "\x1b[1;41m" # white-on-red + elif levelname == "ERROR": + color_code = "\x1b[1;31m" # red + elif levelname in ("WARNING", "WARN"): + color_code = "\x1b[1;33m" # yellow + elif levelname == "INFO": + color_code = "\x1b[1;36m" # cyan + elif levelname == "DEBUG": + color_code = "\x1b[1;37m" # white + else: + color_code = None + + if color_code: + color_reset = "\x1b[0m" # reset + record.levelname = "{}{}{}".format(color_code, record.levelname, color_reset) + + return logging.Formatter.format(self, record) diff --git a/src/env_alias/lib/selector.py b/src/env_alias/lib/selector.py new file mode 100644 index 0000000..f3a587a --- /dev/null +++ b/src/env_alias/lib/selector.py @@ -0,0 +1,84 @@ +import configparser +import json +from functools import reduce +from typing import Union + +import yaml + +from ..exceptions import EnvAliasException + + +class EnvAliasSelector: + @staticmethod + def text_content(content: str, selector: int) -> str: + lines = content.replace("\r", "").split("\n") + if len(lines) < int(selector): + raise EnvAliasException(f"Text content selector {selector!r} is out of range; text has {len(lines)} lines.") + + return lines[int(selector) - 1] + + @staticmethod + def ini_content(content: str, selector_path: Union[str, None]) -> str: + if not selector_path: + raise EnvAliasException("Selector not provided; must define a 'selector' for 'ini' parsing.") + + selector_paths = EnvAliasSelector.__parse_selector_path(selector_path).split(".") + + if len(selector_paths) != 2: + raise EnvAliasException('Selector path for INI content must be in the form "
.