diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..d375709 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,45 @@ +name: Pytest + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Export dependencies to requirements.txt + run: | + poetry export --without-hashes --format=requirements.txt > requirements.txt + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install . + pip install pytest pytest-cov + + - name: Run Pytest + run: | + pytest --cov --junitxml=junit.xml -o junit_family=legacy + + - name: Upload test results to Codecov + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index af332f9..2d326e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ venv dist/ **/__pycache__ **/*.crt +.coverage +junit.xml diff --git a/Makefile b/Makefile index f7882d2..64f7158 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,14 @@ help: ## Print this help run: ## Run the application with enabled debug mode @poetry run kubeseal-auto --debug +.PHONY: test +test: ## Run the tests + @poetry run pytest + +.PHONY: test-coverage +test-coverage: ## Run the tests with coverage + @poetry run pytest --cov --junitxml=junit.xml -o junit_family=legacy + .PHONY: build build: ## Package the application using poetry @poetry build diff --git a/README.md b/README.md index 52de744..f6b6c76 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ kubeseal-auto is an interactive wrapper for kubeseal binary used to encrypt secrets for [sealed-secrets](https://github.com/bitnami-labs/sealed-secrets). -![GitHub last commit (branch)](https://img.shields.io/github/last-commit/shini4i/kubeseal-auto/main?style=plastic) +![GitHub Actions](https://img.shields.io/github/actions/workflow/status/shini4i/kubeseal-auto/run-tests.yml?branch=main) +[![codecov](https://codecov.io/gh/shini4i/kubeseal-auto/graph/badge.svg?token=E61B6OYPFX)](https://codecov.io/gh/shini4i/kubeseal-auto) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/kubeseal-auto?style=plastic) ![PyPI](https://img.shields.io/pypi/v/kubeseal-auto?style=plastic) ![license](https://img.shields.io/github/license/shini4i/kubeseal-auto?style=plastic) diff --git a/poetry.lock b/poetry.lock index 8ecce48..50d5117 100644 --- a/poetry.lock +++ b/poetry.lock @@ -79,6 +79,83 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.6.2" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9df1950fb92d49970cce38100d7e7293c84ed3606eaa16ea0b6bc27175bb667"}, + {file = "coverage-7.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:24500f4b0e03aab60ce575c85365beab64b44d4db837021e08339f61d1fbfe52"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a663b180b6669c400b4630a24cc776f23a992d38ce7ae72ede2a397ce6b0f170"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfde025e2793a22efe8c21f807d276bd1d6a4bcc5ba6f19dbdfc4e7a12160909"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087932079c065d7b8ebadd3a0160656c55954144af6439886c8bcf78bbbcde7f"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9c6b0c1cafd96213a0327cf680acb39f70e452caf8e9a25aeb05316db9c07f89"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6e85830eed5b5263ffa0c62428e43cb844296f3b4461f09e4bdb0d44ec190bc2"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62ab4231c01e156ece1b3a187c87173f31cbeee83a5e1f6dff17f288dca93345"}, + {file = "coverage-7.6.2-cp310-cp310-win32.whl", hash = "sha256:7b80fbb0da3aebde102a37ef0138aeedff45997e22f8962e5f16ae1742852676"}, + {file = "coverage-7.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:d20c3d1f31f14d6962a4e2f549c21d31e670b90f777ef4171be540fb7fb70f02"}, + {file = "coverage-7.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb21bac7783c1bf6f4bbe68b1e0ff0d20e7e7732cfb7995bc8d96e23aa90fc7b"}, + {file = "coverage-7.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b2e437fbd8fae5bc7716b9c7ff97aecc95f0b4d56e4ca08b3c8d8adcaadb84"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:536f77f2bf5797983652d1d55f1a7272a29afcc89e3ae51caa99b2db4e89d658"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f361296ca7054f0936b02525646b2731b32c8074ba6defab524b79b2b7eeac72"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7926d8d034e06b479797c199747dd774d5e86179f2ce44294423327a88d66ca7"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0bbae11c138585c89fb4e991faefb174a80112e1a7557d507aaa07675c62e66b"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fcad7d5d2bbfeae1026b395036a8aa5abf67e8038ae7e6a25c7d0f88b10a8e6a"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f01e53575f27097d75d42de33b1b289c74b16891ce576d767ad8c48d17aeb5e0"}, + {file = "coverage-7.6.2-cp311-cp311-win32.whl", hash = "sha256:7781f4f70c9b0b39e1b129b10c7d43a4e0c91f90c60435e6da8288efc2b73438"}, + {file = "coverage-7.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:9bcd51eeca35a80e76dc5794a9dd7cb04b97f0e8af620d54711793bfc1fbba4b"}, + {file = "coverage-7.6.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ebc94fadbd4a3f4215993326a6a00e47d79889391f5659bf310f55fe5d9f581c"}, + {file = "coverage-7.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9681516288e3dcf0aa7c26231178cc0be6cac9705cac06709f2353c5b406cfea"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9c5d13927d77af4fbe453953810db766f75401e764727e73a6ee4f82527b3e"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92f9ca04b3e719d69b02dc4a69debb795af84cb7afd09c5eb5d54b4a1ae2191"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ff2ef83d6d0b527b5c9dad73819b24a2f76fdddcfd6c4e7a4d7e73ecb0656b4"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47ccb6e99a3031ffbbd6e7cc041e70770b4fe405370c66a54dbf26a500ded80b"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a867d26f06bcd047ef716175b2696b315cb7571ccb951006d61ca80bbc356e9e"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cdfcf2e914e2ba653101157458afd0ad92a16731eeba9a611b5cbb3e7124e74b"}, + {file = "coverage-7.6.2-cp312-cp312-win32.whl", hash = "sha256:f9035695dadfb397bee9eeaf1dc7fbeda483bf7664a7397a629846800ce6e276"}, + {file = "coverage-7.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:5ed69befa9a9fc796fe015a7040c9398722d6b97df73a6b608e9e275fa0932b0"}, + {file = "coverage-7.6.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eea60c79d36a8f39475b1af887663bc3ae4f31289cd216f514ce18d5938df40"}, + {file = "coverage-7.6.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa68a6cdbe1bc6793a9dbfc38302c11599bbe1837392ae9b1d238b9ef3dafcf1"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ec528ae69f0a139690fad6deac8a7d33629fa61ccce693fdd07ddf7e9931fba"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed5ac02126f74d190fa2cc14a9eb2a5d9837d5863920fa472b02eb1595cdc925"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21c0ea0d4db8a36b275cb6fb2437a3715697a4ba3cb7b918d3525cc75f726304"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35a51598f29b2a19e26d0908bd196f771a9b1c5d9a07bf20be0adf28f1ad4f77"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c9192925acc33e146864b8cf037e2ed32a91fdf7644ae875f5d46cd2ef086a5f"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf4eeecc9e10f5403ec06138978235af79c9a79af494eb6b1d60a50b49ed2869"}, + {file = "coverage-7.6.2-cp313-cp313-win32.whl", hash = "sha256:e4ee15b267d2dad3e8759ca441ad450c334f3733304c55210c2a44516e8d5530"}, + {file = "coverage-7.6.2-cp313-cp313-win_amd64.whl", hash = "sha256:c71965d1ced48bf97aab79fad56df82c566b4c498ffc09c2094605727c4b7e36"}, + {file = "coverage-7.6.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7571e8bbecc6ac066256f9de40365ff833553e2e0c0c004f4482facb131820ef"}, + {file = "coverage-7.6.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:078a87519057dacb5d77e333f740708ec2a8f768655f1db07f8dfd28d7a005f0"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5e92e3e84a8718d2de36cd8387459cba9a4508337b8c5f450ce42b87a9e760"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebabdf1c76593a09ee18c1a06cd3022919861365219ea3aca0247ededf6facd6"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12179eb0575b8900912711688e45474f04ab3934aaa7b624dea7b3c511ecc90f"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:39d3b964abfe1519b9d313ab28abf1d02faea26cd14b27f5283849bf59479ff5"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:84c4315577f7cd511d6250ffd0f695c825efe729f4205c0340f7004eda51191f"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff797320dcbff57caa6b2301c3913784a010e13b1f6cf4ab3f563f3c5e7919db"}, + {file = "coverage-7.6.2-cp313-cp313t-win32.whl", hash = "sha256:2b636a301e53964550e2f3094484fa5a96e699db318d65398cfba438c5c92171"}, + {file = "coverage-7.6.2-cp313-cp313t-win_amd64.whl", hash = "sha256:d03a060ac1a08e10589c27d509bbdb35b65f2d7f3f8d81cf2fa199877c7bc58a"}, + {file = "coverage-7.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c37faddc8acd826cfc5e2392531aba734b229741d3daec7f4c777a8f0d4993e5"}, + {file = "coverage-7.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab31fdd643f162c467cfe6a86e9cb5f1965b632e5e65c072d90854ff486d02cf"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97df87e1a20deb75ac7d920c812e9326096aa00a9a4b6d07679b4f1f14b06c90"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:343056c5e0737487a5291f5691f4dfeb25b3e3c8699b4d36b92bb0e586219d14"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4ef1c56b47b6b9024b939d503ab487231df1f722065a48f4fc61832130b90e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fca4a92c8a7a73dee6946471bce6d1443d94155694b893b79e19ca2a540d86e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69f251804e052fc46d29d0e7348cdc5fcbfc4861dc4a1ebedef7e78d241ad39e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e8ea055b3ea046c0f66217af65bc193bbbeca1c8661dc5fd42698db5795d2627"}, + {file = "coverage-7.6.2-cp39-cp39-win32.whl", hash = "sha256:6c2ba1e0c24d8fae8f2cf0aeb2fc0a2a7f69b6d20bd8d3749fd6b36ecef5edf0"}, + {file = "coverage-7.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:2186369a654a15628e9c1c9921409a6b3eda833e4b91f3ca2a7d9f77abb4987c"}, + {file = "coverage-7.6.2-pp39.pp310-none-any.whl", hash = "sha256:667952739daafe9616db19fbedbdb87917eee253ac4f31d70c7587f7ab531b4e"}, + {file = "coverage-7.6.2.tar.gz", hash = "sha256:a5f81e68aa62bc0cfca04f7b19eaa8f9c826b53fc82ab9e2121976dc74f131f3"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "durationpy" version = "0.9" @@ -90,6 +167,20 @@ files = [ {file = "durationpy-0.9.tar.gz", hash = "sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "executing" version = "2.0.1" @@ -155,6 +246,17 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "kubernetes" version = "31.0.0" @@ -198,6 +300,32 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "prompt-toolkit" version = "3.0.36" @@ -252,6 +380,46 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -406,6 +574,17 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + [[package]] name = "urllib3" version = "1.26.19" @@ -452,4 +631,4 @@ test = ["websockets"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3cf86308cc0851c7a911bb615220e3d1d3c0da59b027dbf84bde82a5411e45b4" +content-hash = "f5d79221670aad8cd2c798cc1d6de5e66d13d43c06054f4ae7bec7096c97c637" diff --git a/pyproject.toml b/pyproject.toml index 6610156..ab8aefb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,9 @@ icecream = "^2.1.3" questionary = "^2.0.1" colorama = "^0.4.6" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.3" +pytest-cov = "^5.0.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_kubeseal.py b/tests/test_kubeseal.py new file mode 100644 index 0000000..2a4ea6a --- /dev/null +++ b/tests/test_kubeseal.py @@ -0,0 +1,182 @@ +from unittest.mock import patch, MagicMock, mock_open + +from kubeseal_auto.kubeseal import Kubeseal + + +@patch('subprocess.call') +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubernetes.client.AppsV1Api.list_deployment_for_all_namespaces') +@patch('kubernetes.client.CoreV1Api.list_namespace') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_create_generic_secret(mock_find_controller, mock_list_namespace, mock_list_deployment, + mock_list_kube_config_contexts, mock_load_kube_config, mock_subprocess_call): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_list_deployment.return_value.items = [] + mock_list_namespace.return_value.items = [] + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + secret_params = { + "name": "test-secret", + "namespace": "default", + "type": "generic" + } + secrets_input = 'key1=value1\nkey2=value2' + + with patch('questionary.text') as mock_questionary_text: + mock_questionary_text.return_value.unsafe_ask.return_value = secrets_input + kubeseal.create_generic_secret(secret_params) + mock_subprocess_call.assert_called_once() + command = mock_subprocess_call.call_args[0][0] + assert 'kubectl create secret generic test-secret' in command + assert '--from-literal="key1=value1"' in command + assert '--from-literal="key2=value2"' in command + + +@patch('subprocess.call') +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubernetes.client.AppsV1Api.list_deployment_for_all_namespaces') +@patch('kubernetes.client.CoreV1Api.list_namespace') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_create_tls_secret(mock_find_controller, mock_list_namespace, mock_list_deployment, + mock_list_kube_config_contexts, mock_load_kube_config, mock_subprocess_call): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_list_deployment.return_value.items = [] + mock_list_namespace.return_value.items = [] + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + secret_params = { + "name": "test-tls-secret", + "namespace": "default", + "type": "tls" + } + + kubeseal.create_tls_secret(secret_params) + mock_subprocess_call.assert_called_once() + command = mock_subprocess_call.call_args[0][0] + assert 'kubectl create secret tls test-tls-secret' in command + assert '--namespace default' in command + assert '--key tls.key' in command + assert '--cert tls.crt' in command + assert '--dry-run=client -o yaml' in command + + +@patch('subprocess.call') +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubernetes.client.AppsV1Api.list_deployment_for_all_namespaces') +@patch('kubernetes.client.CoreV1Api.list_namespace') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_create_regcred_secret(mock_find_controller, mock_list_namespace, mock_list_deployment, + mock_list_kube_config_contexts, mock_load_kube_config, mock_subprocess_call): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_list_deployment.return_value.items = [] + mock_list_namespace.return_value.items = [] + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + secret_params = { + "name": "test-regcred-secret", + "namespace": "default", + "type": "docker-registry" + } + + docker_server = "https://index.docker.io/v1/" + docker_username = "testuser" + docker_password = "testpassword" + + with patch('questionary.text') as mock_questionary_text: + mock_questionary_text_instance = MagicMock() + mock_questionary_text.return_value = mock_questionary_text_instance + mock_questionary_text_instance.unsafe_ask.side_effect = [docker_server, docker_username, docker_password] + + kubeseal.create_regcred_secret(secret_params) + mock_subprocess_call.assert_called_once() + command = mock_subprocess_call.call_args[0][0] + assert 'kubectl create secret docker-registry test-regcred-secret' in command + assert '--namespace default' in command + assert f'--docker-server={docker_server}' in command + assert f'--docker-username={docker_username}' in command + assert f'--docker-password={docker_password}' in command + assert '--dry-run=client -o yaml' in command + + +@patch('subprocess.call') +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubernetes.client.AppsV1Api.list_deployment_for_all_namespaces') +@patch('kubernetes.client.CoreV1Api.list_namespace') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_seal(mock_find_controller, mock_list_namespace, mock_list_deployment, mock_list_kube_config_contexts, + mock_load_kube_config, mock_subprocess_call): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_list_deployment.return_value.items = [] + mock_list_namespace.return_value.items = [] + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + secret_name = "test-secret" + + # Mock the open function to simulate the presence of the file + with patch('builtins.open', mock_open(read_data="apiVersion: v1\nkind: Secret\nmetadata:\n name: test-secret")): + kubeseal.seal(secret_name) + mock_subprocess_call.assert_called_once() + command = mock_subprocess_call.call_args[0][0] + assert f"{kubeseal.binary} --format=yaml" in command + assert f"--context={kubeseal.current_context_name}" in command + assert f"--controller-namespace={kubeseal.controller_namespace}" in command + assert f"--controller-name={kubeseal.controller_name}" in command + assert f"< {kubeseal.temp_file.name}" in command + assert f"> {secret_name}.yaml" in command + + +@patch('kubeseal_auto.cluster.Cluster.get_all_namespaces', return_value=['default', 'kube-system']) +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_parse_existing_secret_success(mock_find_controller, mock_list_kube_config_contexts, + mock_load_kube_config, mock_get_all_namespaces): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + with patch('builtins.open', mock_open(read_data="apiVersion: v1\nkind: Secret\nmetadata:\n name: test-secret")): + secret = kubeseal.parse_existing_secret('test-secret.yaml') + assert secret['kind'] == 'Secret' + assert secret['metadata']['name'] == 'test-secret' + + +@patch('kubeseal_auto.cluster.Cluster.get_all_namespaces', return_value=['default', 'kube-system']) +@patch('kubernetes.config.load_kube_config') +@patch('kubernetes.config.list_kube_config_contexts') +@patch('kubeseal_auto.cluster.Cluster._find_sealed_secrets_controller') +def test_parse_existing_secret_file_not_found(mock_find_controller, mock_list_kube_config_contexts, + mock_load_kube_config, mock_get_all_namespaces): + mock_list_kube_config_contexts.return_value = ([{'name': 'context1'}], {'name': 'context2'}) + mock_find_controller.return_value = { + "name": "sealed-secrets-controller", + "namespace": "kube-system", + "version": "v0.12.0" + } + kubeseal = Kubeseal(select_context=False) + with patch('builtins.exit') as mock_exit: + kubeseal.parse_existing_secret('nonexistent-secret.yaml') + mock_exit.assert_called_once_with(1)