From 5603ca2679dab33ac5e25050dc80f7981e20af4b Mon Sep 17 00:00:00 2001 From: Francisco Rivera Date: Thu, 1 Feb 2024 14:16:55 -0300 Subject: [PATCH] [BB-220] Native SSO profiles (#232) * PathsHandler class * configure_sso_profiles feature * refresh_layer_credentials feature * move logic into get_layer_profile + debug messages + expiration comparison fix * move code + basic tests * missing req for CI * update tests * configure_sso_profiles tests * local.tf + tests * list of non dicts bug * refactor + tests * skip lookup references * dont use append mode * simplify refresh logic + tests --- Pipfile | 2 + Pipfile.lock | 1328 +++++++++++++---------- dev-requirements.txt | 4 +- leverage/_utils.py | 37 +- leverage/container.py | 261 ++--- leverage/containers/kubectl.py | 14 +- leverage/leverage.py | 3 +- leverage/modules/__init__.py | 1 - leverage/modules/auth.py | 150 +++ leverage/modules/aws.py | 119 +- leverage/modules/kubectl.py | 2 +- leverage/modules/terraform.py | 65 +- leverage/path.py | 154 +++ setup.cfg | 2 + tests/test_containers/test_kubectl.py | 27 +- tests/test_containers/test_terraform.py | 32 +- tests/test_modules/test_auth.py | 267 +++++ tests/test_modules/test_terraform.py | 2 +- tests/test_path.py | 30 +- tests/test_utils.py | 86 ++ 20 files changed, 1717 insertions(+), 869 deletions(-) create mode 100644 leverage/modules/auth.py create mode 100644 tests/test_modules/test_auth.py create mode 100644 tests/test_utils.py diff --git a/Pipfile b/Pipfile index 9dcead0d..f0c6f386 100644 --- a/Pipfile +++ b/Pipfile @@ -26,3 +26,5 @@ docker = "==6.1.0" dockerpty = "==0.4.1" questionary = "==1.10.0" python-hcl2 = "==3.0.1" +boto3 = "==1.33.2" +configupdater = "==3.2" diff --git a/Pipfile.lock b/Pipfile.lock index f7993b80..0e8d37ee 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b7dbbf0ab19d88ed3d318aef5d0e0580c89a934327bcad66b4681aee96084377" + "sha256": "c4c567df84a8dff9c9e4454078efd4a2f5d8d49ddbc7253d5b9c999b5eaa26cb" }, "pipfile-spec": 6, "requires": { @@ -16,94 +16,125 @@ ] }, "default": { + "boto3": { + "hashes": [ + "sha256:70626598dd6698d6da8f2854a1ae5010f175572e2a465b2aa86685c745c1013c", + "sha256:fc7c0dd5fa74ae0d57e11747695bdba4ad164e62dee35db15b43762c392fbd92" + ], + "index": "pypi", + "version": "==1.33.2" + }, + "botocore": { + "hashes": [ + "sha256:872decbc760c3b2942477cda905d4443bd8a97511dcee3e9ca09eeb9299ad5e2", + "sha256:bbd96c211b670d17191d617423f3c4c277964eeb586a33b3759691c33904629d" + ], + "markers": "python_version >= '3.7'", + "version": "==1.33.4" + }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2023.11.17" }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.1.0" + "version": "==3.3.2" }, "click": { "hashes": [ @@ -128,6 +159,14 @@ ], "version": "==0.9.1" }, + "configupdater": { + "hashes": [ + "sha256:0f65a041627d7693840b4dd743581db4c441c97195298a29d075f91b79539df2", + "sha256:9fdac53831c1b062929bf398b649b87ca30e7f1a735f3fbf482072804106306b" + ], + "index": "pypi", + "version": "==3.2" + }, "docker": { "hashes": [ "sha256:b65c999f87cb5c31700b6944dc17a631071170d1aab3ad6e23506068579f885d", @@ -145,11 +184,11 @@ }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.6" }, "jinja2": { "hashes": [ @@ -159,6 +198,14 @@ "index": "pypi", "version": "==3.0.1" }, + "jmespath": { + "hashes": [ + "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", + "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.1" + }, "lark-parser": { "hashes": [ "sha256:42f367612a1bbc4cf9d8c8eb1b209d8a9b397d55af75620c9e6f53e502235996" @@ -167,83 +214,101 @@ }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "version": "==2.1.3" }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "prompt-toolkit": { "hashes": [ - "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b", - "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f" + "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0", + "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.38" + "version": "==3.0.41" }, "pygments": { "hashes": [ - "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" ], "markers": "python_version >= '3.7'", - "version": "==2.15.1" + "version": "==2.17.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" }, "python-hcl2": { "hashes": [ @@ -262,11 +327,11 @@ }, "requests": { "hashes": [ - "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294", - "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.31.0" }, "rich": { "hashes": [ @@ -286,45 +351,67 @@ }, "ruamel.yaml.clib": { "hashes": [ - "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", - "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", - "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", - "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", - "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", - "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", - "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", - "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", - "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", - "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", - "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", - "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", - "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", - "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", - "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", - "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", - "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", - "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", - "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", - "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", - "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", - "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", - "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", - "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", - "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", - "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", - "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", - "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", - "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", - "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", - "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", - "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", - "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", - "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", - "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" + "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", + "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", + "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", + "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", + "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", + "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", + "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", + "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", + "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", + "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", + "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", + "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7", + "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", + "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", + "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", + "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", + "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6", + "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", + "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", + "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", + "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3", + "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", + "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", + "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", + "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", + "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", + "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", + "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", + "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", + "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", + "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", + "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", + "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", + "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", + "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" ], "markers": "python_version < '3.10' and platform_python_implementation == 'CPython'", - "version": "==0.2.7" + "version": "==0.2.8" + }, + "s3transfer": { + "hashes": [ + "sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283", + "sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76" + ], + "markers": "python_version >= '3.7'", + "version": "==0.8.2" }, "six": { "hashes": [ @@ -336,26 +423,26 @@ }, "urllib3": { "hashes": [ - "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", - "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" }, "wcwidth": { "hashes": [ - "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", - "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02", + "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c" ], - "version": "==0.2.6" + "version": "==0.2.12" }, "websocket-client": { "hashes": [ - "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40", - "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e" + "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24", + "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df" ], - "markers": "python_version >= '3.7'", - "version": "==1.5.1" + "markers": "python_version >= '3.8'", + "version": "==1.6.4" }, "yaenv": { "hashes": [ @@ -414,171 +501,183 @@ "index": "pypi", "version": "==23.3.0" }, - "bleach": { + "boto3": { + "hashes": [ + "sha256:70626598dd6698d6da8f2854a1ae5010f175572e2a465b2aa86685c745c1013c", + "sha256:fc7c0dd5fa74ae0d57e11747695bdba4ad164e62dee35db15b43762c392fbd92" + ], + "index": "pypi", + "version": "==1.33.2" + }, + "botocore": { "hashes": [ - "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414", - "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4" + "sha256:872decbc760c3b2942477cda905d4443bd8a97511dcee3e9ca09eeb9299ad5e2", + "sha256:bbd96c211b670d17191d617423f3c4c277964eeb586a33b3759691c33904629d" ], "markers": "python_version >= '3.7'", - "version": "==6.0.0" + "version": "==1.33.4" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2023.11.17" }, "cffi": { "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "version": "==1.15.1" + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "python_version >= '3.8'", + "version": "==1.16.0" }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.1.0" + "version": "==3.3.2" }, "click": { "hashes": [ @@ -605,85 +704,90 @@ }, "coverage": { "hashes": [ - "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3", - "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a", - "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813", - "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0", - "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a", - "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd", - "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139", - "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b", - "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252", - "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790", - "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045", - "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce", - "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200", - "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718", - "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b", - "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f", - "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5", - "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade", - "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5", - "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a", - "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8", - "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33", - "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e", - "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c", - "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3", - "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969", - "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068", - "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2", - "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771", - "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed", - "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212", - "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614", - "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88", - "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3", - "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c", - "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84", - "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11", - "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1", - "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1", - "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e", - "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1", - "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd", - "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47", - "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a", - "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c", - "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31", - "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5", - "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6", - "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303", - "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5", - "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47" - ], - "markers": "python_version >= '3.7'", - "version": "==7.2.5" + "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1", + "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63", + "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9", + "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312", + "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3", + "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb", + "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25", + "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92", + "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda", + "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148", + "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6", + "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216", + "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a", + "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640", + "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836", + "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c", + "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f", + "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2", + "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901", + "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed", + "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a", + "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074", + "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc", + "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84", + "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083", + "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f", + "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c", + "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c", + "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637", + "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2", + "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82", + "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f", + "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce", + "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef", + "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f", + "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611", + "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c", + "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76", + "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9", + "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce", + "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9", + "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf", + "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf", + "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9", + "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6", + "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2", + "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a", + "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a", + "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf", + "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738", + "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a", + "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4" + ], + "markers": "python_version >= '3.8'", + "version": "==7.3.2" }, "cryptography": { "hashes": [ - "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440", - "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288", - "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b", - "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958", - "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b", - "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d", - "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a", - "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404", - "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b", - "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e", - "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2", - "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c", - "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b", - "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9", - "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b", - "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636", - "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99", - "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e", - "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9" + "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960", + "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a", + "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc", + "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a", + "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf", + "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1", + "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39", + "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406", + "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a", + "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a", + "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c", + "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be", + "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15", + "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2", + "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d", + "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157", + "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003", + "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248", + "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a", + "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec", + "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309", + "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7", + "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d" ], - "markers": "python_version >= '3.6'", - "version": "==40.0.2" + "markers": "python_version >= '3.7'", + "version": "==41.0.7" }, "docker": { "hashes": [ @@ -702,35 +806,35 @@ }, "docutils": { "hashes": [ - "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", - "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" + "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", + "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" ], "markers": "python_version >= '3.7'", - "version": "==0.19" + "version": "==0.20.1" }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.6" }, "importlib-metadata": { "hashes": [ - "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed", - "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705" + "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", + "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743" ], - "markers": "python_version >= '3.7'", - "version": "==6.6.0" + "markers": "python_version >= '3.8'", + "version": "==6.8.0" }, "importlib-resources": { "hashes": [ - "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6", - "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a" + "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a", + "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6" ], "markers": "python_version < '3.9'", - "version": "==5.12.0" + "version": "==6.1.1" }, "iniconfig": { "hashes": [ @@ -750,11 +854,11 @@ }, "jaraco.classes": { "hashes": [ - "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158", - "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a" + "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb", + "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621" ], - "markers": "python_version >= '3.7'", - "version": "==3.2.3" + "markers": "python_version >= '3.8'", + "version": "==3.3.0" }, "jeepney": { "hashes": [ @@ -772,13 +876,21 @@ "index": "pypi", "version": "==3.0.1" }, - "keyring": { + "jmespath": { "hashes": [ - "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd", - "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678" + "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", + "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" ], "markers": "python_version >= '3.7'", - "version": "==23.13.1" + "version": "==1.0.1" + }, + "keyring": { + "hashes": [ + "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836", + "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25" + ], + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "lark-parser": { "hashes": [ @@ -834,59 +946,69 @@ }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "version": "==2.1.3" }, "mccabe": { "hashes": [ @@ -897,11 +1019,11 @@ }, "more-itertools": { "hashes": [ - "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", - "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" + "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a", + "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6" ], - "markers": "python_version >= '3.7'", - "version": "==9.1.0" + "markers": "python_version >= '3.8'", + "version": "==10.1.0" }, "mypy-extensions": { "hashes": [ @@ -911,21 +1033,42 @@ "markers": "python_version >= '3.5'", "version": "==1.0.0" }, + "nh3": { + "hashes": [ + "sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873", + "sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad", + "sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5", + "sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525", + "sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2", + "sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e", + "sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d", + "sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450", + "sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e", + "sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6", + "sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a", + "sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4", + "sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4", + "sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6", + "sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e", + "sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75" + ], + "version": "==0.2.14" + }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pathspec": { "hashes": [ - "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", - "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], "markers": "python_version >= '3.7'", - "version": "==0.11.1" + "version": "==0.11.2" }, "pkginfo": { "hashes": [ @@ -937,11 +1080,11 @@ }, "platformdirs": { "hashes": [ - "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4", - "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335" + "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", + "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731" ], "markers": "python_version >= '3.7'", - "version": "==3.5.0" + "version": "==4.0.0" }, "pluggy": { "hashes": [ @@ -953,11 +1096,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b", - "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f" + "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0", + "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.38" + "version": "==3.0.41" }, "py": { "hashes": [ @@ -976,11 +1119,11 @@ }, "pygments": { "hashes": [ - "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" ], "markers": "python_version >= '3.7'", - "version": "==2.15.1" + "version": "==2.17.2" }, "pylint": { "hashes": [ @@ -1006,6 +1149,14 @@ "index": "pypi", "version": "==2.12.1" }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, "python-hcl2": { "hashes": [ "sha256:f6c178d216e9d8ef1ef9fceeb16792cbabfb69d2ae1a73e263974ab6f74489ab" @@ -1023,19 +1174,19 @@ }, "readme-renderer": { "hashes": [ - "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273", - "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343" + "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d", + "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1" ], - "markers": "python_version >= '3.7'", - "version": "==37.3" + "markers": "python_version >= '3.8'", + "version": "==42.0" }, "requests": { "hashes": [ - "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294", - "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.31.0" }, "requests-toolbelt": { "hashes": [ @@ -1071,45 +1222,67 @@ }, "ruamel.yaml.clib": { "hashes": [ - "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", - "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", - "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", - "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", - "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", - "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", - "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", - "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", - "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", - "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", - "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", - "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", - "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", - "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", - "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", - "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", - "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", - "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", - "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", - "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", - "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", - "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", - "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", - "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", - "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", - "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", - "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", - "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", - "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", - "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", - "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", - "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", - "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", - "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", - "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" + "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", + "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", + "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", + "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", + "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", + "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", + "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", + "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", + "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", + "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", + "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", + "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7", + "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", + "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", + "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", + "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", + "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6", + "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", + "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", + "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", + "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3", + "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", + "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", + "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", + "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", + "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", + "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", + "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", + "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", + "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", + "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", + "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", + "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", + "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", + "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" ], "markers": "python_version < '3.10' and platform_python_implementation == 'CPython'", - "version": "==0.2.7" + "version": "==0.2.8" + }, + "s3transfer": { + "hashes": [ + "sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283", + "sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76" + ], + "markers": "python_version >= '3.7'", + "version": "==0.8.2" }, "secretstorage": { "hashes": [ @@ -1153,11 +1326,11 @@ }, "tqdm": { "hashes": [ - "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", - "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" + "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386", + "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7" ], "markers": "python_version >= '3.7'", - "version": "==4.65.0" + "version": "==4.66.1" }, "twine": { "hashes": [ @@ -1169,41 +1342,34 @@ }, "typing-extensions": { "hashes": [ - "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", - "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], "markers": "python_version < '3.10'", - "version": "==4.5.0" + "version": "==4.8.0" }, "urllib3": { "hashes": [ - "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", - "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", + "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.18" }, "wcwidth": { "hashes": [ - "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", - "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02", + "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c" ], - "version": "==0.2.6" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" + "version": "==0.2.12" }, "websocket-client": { "hashes": [ - "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40", - "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e" + "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24", + "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df" ], - "markers": "python_version >= '3.7'", - "version": "==1.5.1" + "markers": "python_version >= '3.8'", + "version": "==1.6.4" }, "wheel": { "hashes": [ @@ -1229,11 +1395,11 @@ }, "zipp": { "hashes": [ - "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", - "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" + "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", + "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" ], - "markers": "python_version >= '3.7'", - "version": "==3.15.0" + "markers": "python_version >= '3.8'", + "version": "==3.17.0" } } } diff --git a/dev-requirements.txt b/dev-requirements.txt index c814582f..859d5910 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,4 +10,6 @@ ruamel.yaml==0.17.10 docker==5.0.0 dockerpty==0.4.1 questionary==1.10.0 -python-hcl2 == 3.0.1 \ No newline at end of file +python-hcl2==3.0.1 +configupdater==3.2 +boto3==1.33.2 diff --git a/leverage/_utils.py b/leverage/_utils.py index 322bc718..c67442da 100644 --- a/leverage/_utils.py +++ b/leverage/_utils.py @@ -9,6 +9,7 @@ from subprocess import PIPE from click.exceptions import Exit +from configupdater import ConfigUpdater from docker import DockerClient from docker.models.containers import Container @@ -109,7 +110,7 @@ def __exit__(self, *args, **kwargs): } ) # now return file ownership on the aws credentials files - self.container.change_file_ownership(self.container.guest_aws_credentials_dir) + self.container.change_file_ownership(self.container.paths.guest_aws_credentials_dir) class AwsCredsContainer: @@ -136,7 +137,7 @@ def __enter__(self): def __exit__(self, *args, **kwargs): # now return file ownership on the aws credentials files - self.tf_container.change_file_ownership(self.tf_container.guest_aws_credentials_dir) + self.tf_container.change_file_ownership(self.tf_container.paths.guest_aws_credentials_dir) class ExitError(Exit): @@ -200,3 +201,35 @@ def tar_directory(host_dir_path: Path) -> bytes: bytes_array.seek(0) # return the whole tar file as a byte array return bytes_array.read() + + +def key_finder(d: dict, target: str, avoid: str = None): + """ + Iterate over a dict of dicts and/or lists of dicts, looking for a key with value "target". + Collect and return all the values that matches "target" as key. + """ + values = [] + + for key, value in d.items(): + if isinstance(value, dict): + # not the target but a dict? keep iterating recursively + values.extend(key_finder(value, target, avoid)) + elif isinstance(value, list): + # not a dict but a list? it must be a list of dicts, keep iterating recursively + for dict_ in [d_ for d_ in value if isinstance(d_, dict)]: + values.extend(key_finder(dict_, target, avoid)) + elif key == target: + if avoid and avoid in value: + # we found a key but the value contains so skip it + continue + # found the target key, store the value + return [value] # return it as an 1-item array to avoid .extend() to split the string + + return values + + +def get_or_create_section(updater: ConfigUpdater, section_name: str): + if not updater.has_section(section_name): + updater.add_section(section_name) + # add_section doesn't return the section object, so we need to retrieve it either case + return updater.get_section(section_name) diff --git a/leverage/container.py b/leverage/container.py index 2a29c988..5e3964f3 100644 --- a/leverage/container.py +++ b/leverage/container.py @@ -9,6 +9,7 @@ import hcl2 from click.exceptions import Exit import dockerpty +from configupdater import ConfigUpdater from docker import DockerClient from docker.errors import APIError, NotFound from docker.types import Mount @@ -17,13 +18,10 @@ from leverage import __toolbox_version__ from leverage import logger from leverage._utils import AwsCredsEntryPoint, CustomEntryPoint, ExitError, ContainerSession +from leverage.modules.auth import refresh_layer_credentials from leverage.logger import console, raw_logger from leverage.logger import get_script_log_level -from leverage.path import get_root_path -from leverage.path import get_account_path -from leverage.path import get_global_config_path -from leverage.path import get_account_config_path -from leverage.path import NotARepositoryError +from leverage.path import PathsHandler from leverage.conf import load as load_env REGION = ( @@ -68,40 +66,20 @@ class LeverageContainer: """ LEVERAGE_IMAGE = "binbash/leverage-toolbox" - - COMMON_TFVARS = "common.tfvars" - ACCOUNT_TFVARS = "account.tfvars" - BACKEND_TFVARS = "backend.tfvars" - SHELL = "/bin/bash" - def __init__(self, client, mounts: dict = None, env_vars: dict = None): + def __init__(self, client, mounts: tuple = None, env_vars: dict = None): """Project related paths are determined and stored. Project configuration is loaded. Args: client (docker.DockerClient): Client to interact with Docker daemon. """ self.client = client - - self.home = Path.home() - self.cwd = Path.cwd() - try: - self.root_dir = Path(get_root_path()) - self.account_dir = Path(get_account_path()) - self.common_config_dir = Path(get_global_config_path()) - self.account_config_dir = Path(get_account_config_path()) - except NotARepositoryError: - logger.error("Out of Leverage project context. Please cd into a Leverage project directory first.") - raise Exit(1) - # Load configs self.env_conf = load_env() - common_config = self.common_config_dir / self.COMMON_TFVARS - self.common_conf = hcl2.loads(common_config.read_text()) if common_config.exists() else {} - - account_config = self.account_config_dir / self.ACCOUNT_TFVARS - self.account_conf = hcl2.loads(account_config.read_text()) if account_config.exists() else {} + self.paths = PathsHandler(self.env_conf) + self.project = self.paths.project # Set image to use self.image = self.env_conf.get("TERRAFORM_IMAGE", self.LEVERAGE_IMAGE) @@ -113,21 +91,6 @@ def __init__(self, client, mounts: dict = None, env_vars: dict = None): ) raise Exit(1) - # Get project name - self.project = self.common_conf.get("project", self.env_conf.get("PROJECT", False)) - if not self.project: - logger.error("Project name has not been set. Exiting.") - raise Exit(1) - - # Project mount location - self.guest_base_path = f"/{self.common_conf.get('project_long', 'project')}" - - # Ensure credentials directory - self.host_aws_credentials_dir = self.home / ".aws" / self.project - if not self.host_aws_credentials_dir.exists(): - self.host_aws_credentials_dir.mkdir(parents=True) - self.sso_cache = self.host_aws_credentials_dir / "sso" / "cache" - mounts = [Mount(source=source, target=target, type="bind") for source, target in mounts] if mounts else [] self.host_config = self.client.api.create_host_config(security_opt=["label:disable"], mounts=mounts) self.container_config = { @@ -136,7 +99,7 @@ def __init__(self, client, mounts: dict = None, env_vars: dict = None): "stdin_open": True, "environment": env_vars or {}, "entrypoint": "", - "working_dir": f"{self.guest_base_path}/{self.cwd.relative_to(self.root_dir).as_posix()}", + "working_dir": f"{self.paths.guest_base_path}/{self.paths.cwd.relative_to(self.paths.root_dir).as_posix()}", "host_config": self.host_config, } @@ -164,37 +127,16 @@ def mounts(self): def mounts(self, value): self.container_config["host_config"]["Mounts"] = value - @property - def guest_account_base_path(self): - return f"{self.guest_base_path}/{self.account_dir.relative_to(self.root_dir).as_posix()}" - - @property - def common_tfvars(self): - return f"{self.guest_base_path}/config/{self.COMMON_TFVARS}" - - @property - def account_tfvars(self): - return f"{self.guest_account_base_path}/config/{self.ACCOUNT_TFVARS}" - - @property - def backend_tfvars(self): - return f"{self.guest_account_base_path}/config/{self.BACKEND_TFVARS}" - - @property - def guest_aws_credentials_dir(self): - return f"/root/tmp/{self.project}" - @property def region(self): """ Return the region of the layer. """ - if matches := re.match(REGION, self.cwd.as_posix()): + if matches := re.match(REGION, self.paths.cwd.as_posix()): # the region (group 1) is between the projects folders (group 0) and the layers (group 2) return matches.groups()[1] - logger.exception(f"No valid region could be found at: {self.cwd.as_posix()}") - raise Exit(1) + raise ExitError(1, f"No valid region could be found at: {self.paths.cwd.as_posix()}") def ensure_image(self): """Make sure the required Docker image is available in the system. If not, pull it from registry.""" @@ -343,29 +285,6 @@ def exec(self, command: str, *arguments) -> Tuple[int, str]: def docker_logs(self, container): return self.client.api.logs(container).decode("utf-8") - def get_location_type(self): - """ - Returns the location type: - - root - - account - - config - - layer - - sublayer - - not a project - """ - if self.cwd == self.root_dir: - return "root" - elif self.cwd == self.account_dir: - return "account" - elif self.cwd in (self.common_config_dir, self.account_config_dir): - return "config" - elif (self.cwd.as_posix().find(self.account_dir.as_posix()) >= 0) and list(self.cwd.glob("*.tf")): - return "layer" - elif (self.cwd.as_posix().find(self.account_dir.as_posix()) >= 0) and not list(self.cwd.glob("*.tf")): - return "layers-group" - else: - return "not a project" - def change_ownership_cmd(self, path: Union[Path, str], recursive=True) -> str: recursive = "-R " if recursive else "" user_id = os.getuid() @@ -383,7 +302,22 @@ def change_file_ownership(self, path: Union[Path, str], recursive=True): self._exec(cmd) -class AWSCLIContainer(LeverageContainer): +class SSOContainer(LeverageContainer): + def get_sso_access_token(self): + with open(self.paths.sso_token_file) as token_file: + return json.loads(token_file.read())["accessToken"] + + @property + def sso_region_from_main_profile(self): + """ + Same than AWSCLIContainer.get_sso_region but without using a container. + """ + conf = ConfigUpdater() + conf.read(self.paths.host_aws_profiles_file) + return conf.get(f"profile {self.project}-sso", "sso_region").value + + +class AWSCLIContainer(SSOContainer): """Leverage Container specially tailored to run AWS CLI commands.""" AWS_CLI_BINARY = "/usr/local/bin/aws" @@ -391,7 +325,6 @@ class AWSCLIContainer(LeverageContainer): # SSO scripts AWS_SSO_LOGIN_SCRIPT = "/root/scripts/aws-sso/aws-sso-login.sh" AWS_SSO_LOGOUT_SCRIPT = "/root/scripts/aws-sso/aws-sso-logout.sh" - AWS_SSO_CONFIGURE_SCRIPT = "/root/scripts/aws-sso/aws-sso-configure.sh" # SSO constants AWS_SSO_LOGIN_URL = "https://device.sso.{region}.amazonaws.com/?user_code={user_code}" @@ -403,18 +336,22 @@ def __init__(self, client): super().__init__(client) self.environment = { - "COMMON_CONFIG_FILE": self.common_tfvars, - "ACCOUNT_CONFIG_FILE": self.account_tfvars, - "BACKEND_CONFIG_FILE": self.backend_tfvars, - "AWS_SHARED_CREDENTIALS_FILE": f"{self.guest_aws_credentials_dir}/credentials", - "AWS_CONFIG_FILE": f"{self.guest_aws_credentials_dir}/config", - "SSO_CACHE_DIR": f"{self.guest_aws_credentials_dir}/sso/cache", + "COMMON_CONFIG_FILE": self.paths.common_tfvars, + "ACCOUNT_CONFIG_FILE": self.paths.account_tfvars, + "BACKEND_CONFIG_FILE": self.paths.backend_tfvars, + "AWS_SHARED_CREDENTIALS_FILE": f"{self.paths.guest_aws_credentials_dir}/credentials", + "AWS_CONFIG_FILE": f"{self.paths.guest_aws_credentials_dir}/config", + "SSO_CACHE_DIR": f"{self.paths.guest_aws_credentials_dir}/sso/cache", "SCRIPT_LOG_LEVEL": get_script_log_level(), } self.entrypoint = self.AWS_CLI_BINARY self.mounts = [ - Mount(source=self.root_dir.as_posix(), target=self.guest_base_path, type="bind"), - Mount(source=self.host_aws_credentials_dir.as_posix(), target=self.guest_aws_credentials_dir, type="bind"), + Mount(source=self.paths.root_dir.as_posix(), target=self.paths.guest_base_path, type="bind"), + Mount( + source=self.paths.host_aws_credentials_dir.as_posix(), + target=self.paths.guest_aws_credentials_dir, + type="bind", + ), ] logger.debug(f"[bold cyan]Container configuration:[/bold cyan]\n{json.dumps(self.container_config, indent=2)}") @@ -486,12 +423,12 @@ def sso_login(self) -> int: exit_code = self.client.api.wait(container)["StatusCode"] raw_logger.info(self.docker_logs(container)) # now return ownership of the token file back to the user - self.change_file_ownership(self.guest_aws_credentials_dir) + self.change_file_ownership(self.paths.guest_aws_credentials_dir) return exit_code -class TerraformContainer(LeverageContainer): +class TerraformContainer(SSOContainer): """Leverage container specifically tailored to run Terraform commands. It handles authentication and some checks regarding where the command is being executed.""" @@ -503,12 +440,10 @@ class TerraformContainer(LeverageContainer): def __init__(self, client, mounts=None, env_vars=None): super().__init__(client, mounts=mounts, env_vars=env_vars) - if self.root_dir == self.account_dir == self.common_config_dir == self.account_config_dir == self.cwd: - logger.error("Not running in a Leverage project. Exiting.") - raise Exit(1) + self.paths.assert_running_leverage_project() # Set authentication methods - self.sso_enabled = self.common_conf.get("sso_enabled", False) + self.sso_enabled = self.paths.common_conf.get("sso_enabled", False) self.mfa_enabled = ( self.env_conf.get("MFA_ENABLED", "false") == "true" ) # TODO: Convert values to bool upon loading @@ -518,15 +453,15 @@ def __init__(self, client, mounts=None, env_vars=None): self.environment.update( { - "COMMON_CONFIG_FILE": self.common_tfvars, - "ACCOUNT_CONFIG_FILE": self.account_tfvars, - "BACKEND_CONFIG_FILE": self.backend_tfvars, - "AWS_SHARED_CREDENTIALS_FILE": f"{self.guest_aws_credentials_dir}/credentials", - "AWS_CONFIG_FILE": f"{self.guest_aws_credentials_dir}/config", - "SRC_AWS_SHARED_CREDENTIALS_FILE": f"{self.guest_aws_credentials_dir}/credentials", # Legacy? - "SRC_AWS_CONFIG_FILE": f"{self.guest_aws_credentials_dir}/config", # Legacy? - "AWS_CACHE_DIR": f"{self.guest_aws_credentials_dir}/cache", - "SSO_CACHE_DIR": f"{self.guest_aws_credentials_dir}/sso/cache", + "COMMON_CONFIG_FILE": self.paths.common_tfvars, + "ACCOUNT_CONFIG_FILE": self.paths.account_tfvars, + "BACKEND_CONFIG_FILE": self.paths.backend_tfvars, + "AWS_SHARED_CREDENTIALS_FILE": f"{self.paths.guest_aws_credentials_dir}/credentials", + "AWS_CONFIG_FILE": f"{self.paths.guest_aws_credentials_dir}/config", + "SRC_AWS_SHARED_CREDENTIALS_FILE": f"{self.paths.guest_aws_credentials_dir}/credentials", # Legacy? + "SRC_AWS_CONFIG_FILE": f"{self.paths.guest_aws_credentials_dir}/config", # Legacy? + "AWS_CACHE_DIR": f"{self.paths.guest_aws_credentials_dir}/cache", + "SSO_CACHE_DIR": f"{self.paths.guest_aws_credentials_dir}/sso/cache", "SCRIPT_LOG_LEVEL": get_script_log_level(), "MFA_SCRIPT_LOG_LEVEL": get_script_log_level(), # Legacy "SSH_AUTH_SOCK": "" if SSH_AUTH_SOCK is None else "/ssh-agent", @@ -534,20 +469,24 @@ def __init__(self, client, mounts=None, env_vars=None): ) self.entrypoint = self.TF_BINARY extra_mounts = [ - Mount(source=self.root_dir.as_posix(), target=self.guest_base_path, type="bind"), - Mount(source=self.host_aws_credentials_dir.as_posix(), target=self.guest_aws_credentials_dir, type="bind"), - Mount(source=(self.home / ".gitconfig").as_posix(), target="/etc/gitconfig", type="bind"), + Mount(source=self.paths.root_dir.as_posix(), target=self.paths.guest_base_path, type="bind"), + Mount( + source=self.paths.host_aws_credentials_dir.as_posix(), + target=self.paths.guest_aws_credentials_dir, + type="bind", + ), + Mount(source=(self.paths.home / ".gitconfig").as_posix(), target="/etc/gitconfig", type="bind"), ] self.mounts.extend(extra_mounts) # if you have set the tf plugin cache locally - if self.tf_cache_dir: + if self.paths.tf_cache_dir: # then mount it too into the container - self.environment["TF_PLUGIN_CACHE_DIR"] = self.tf_cache_dir + self.environment["TF_PLUGIN_CACHE_DIR"] = self.paths.tf_cache_dir # given that terraform use symlinks to point from the .terraform folder into the plugin folder # we need to use the same directory inside the container # otherwise symlinks will be broken once outside the container # which will break terraform usage outside Leverage - self.mounts.append(Mount(source=self.tf_cache_dir, target=self.tf_cache_dir, type="bind")) + self.mounts.append(Mount(source=self.paths.tf_cache_dir, target=self.paths.tf_cache_dir, type="bind")) if SSH_AUTH_SOCK is not None: self.mounts.append(Mount(source=SSH_AUTH_SOCK, target="/ssh-agent", type="bind")) @@ -564,7 +503,8 @@ def auth_method(self) -> str: """ if self.sso_enabled: self._check_sso_token() - return f"{self.TF_SSO_ENTRYPOINT} -- " + # sso credentials needs to be refreshed right before we execute our command on the container + refresh_layer_credentials(self) elif self.mfa_enabled: self.environment.update( { @@ -578,42 +518,16 @@ def auth_method(self) -> str: return "" - @property - def tf_cache_dir(self): - return os.getenv("TF_PLUGIN_CACHE_DIR") - - def _guest_config_file(self, file): - """Map config file in host to location in guest. - - Args: - file (pathlib.Path): File in host to map - - Raises: - Exit: If file is not contained in any valid config directory - - Returns: - str: Path in guest to config file - """ - file_name = file.name - - if file.parent == self.account_config_dir: - return f"{self.guest_account_base_path}/config/{file_name}" - if file.parent == self.common_config_dir: - return f"{self.guest_base_path}/config/{file_name}" - - logger.error("File is not part of any config directory.") - raise Exit(1) - @property def tf_default_args(self): """Array of strings containing all valid config files for layer as parameters for Terraform""" common_config_files = [ - f"-var-file={self._guest_config_file(common_file)}" - for common_file in self.common_config_dir.glob("*.tfvars") + f"-var-file={self.paths.guest_config_file(common_file)}" + for common_file in self.paths.common_config_dir.glob("*.tfvars") ] account_config_files = [ - f"-var-file={self._guest_config_file(account_file)}" - for account_file in self.account_config_dir.glob("*.tfvars") + f"-var-file={self.paths.guest_config_file(account_file)}" + for account_file in self.paths.account_config_dir.glob("*.tfvars") ] return common_config_files + account_config_files @@ -636,17 +550,17 @@ def _check_sso_token(self): # Adding `token` file name to this function in order to # meet the requirement regarding to have just one # token file in the sso/cache - sso_role = self.account_conf.get("sso_role") - token_file = self.sso_cache / sso_role + sso_role = self.paths.account_conf.get("sso_role") + token_file = self.paths.sso_cache / sso_role - token_files = list(self.sso_cache.glob("*")) + token_files = list(self.paths.sso_cache.glob("*")) if not token_files: logger.error("No AWS SSO token found. Please log in or configure SSO.") raise Exit(1) if token_file not in token_files: sso_role = "token" - token_file = self.sso_cache / sso_role + token_file = self.paths.sso_cache / sso_role if token_file not in token_files: logger.error( "No valid AWS SSO token found for current account.\n" @@ -671,23 +585,6 @@ def _check_sso_token(self): self.entrypoint = entrypoint - def check_for_layer_location(self): - """Make sure the command is being ran at layer level. If not, bail.""" - if self.cwd in (self.common_config_dir, self.account_config_dir): - logger.error("Currently in a configuration directory, no Terraform command can be run here.") - raise Exit(1) - - if self.cwd in (self.root_dir, self.account_dir): - logger.error( - "Terraform commands cannot run neither in the root of the project or in" - " the root directory of an account." - ) - raise Exit(1) - - if not list(self.cwd.glob("*.tf")): - logger.error("This command can only run at [bold]layer[/bold] level.") - raise Exit(1) - def refresh_credentials(self): with AwsCredsEntryPoint(self, override_entrypoint=""): if exit_code := self._start('echo "Done."'): @@ -699,7 +596,7 @@ def start(self, command, *arguments): def start_in_layer(self, command, *arguments): """Run a command that can only be performed in layer level.""" - self.check_for_layer_location() + self.paths.check_for_layer_location() return self.start(command, *arguments) @@ -720,7 +617,7 @@ def system_exec(self, command): def start_shell(self): """Launch a shell in the container.""" if self.mfa_enabled or self.sso_enabled: - self.check_for_layer_location() + self.paths.check_for_layer_location() with AwsCredsEntryPoint(self, override_entrypoint=""): self._start(self.SHELL) @@ -734,7 +631,7 @@ def set_backend_key(self, skip_validation=False): # 2 | true | false | false/true | set the key # 3 | true | true | false/true | read the key try: - config_tf_file = self.cwd / "config.tf" + config_tf_file = self.paths.cwd / "config.tf" config_tf = hcl2.loads(config_tf_file.read_text()) if config_tf_file.exists() else {} if ( "terraform" in config_tf @@ -745,12 +642,14 @@ def set_backend_key(self, skip_validation=False): backend_key = config_tf["terraform"][0]["backend"][0]["s3"]["key"] self._backend_key = backend_key else: - self._backend_key = f"{self.cwd.relative_to(self.root_dir).as_posix()}/terraform.tfstate".replace( - "/base-", "/" - ).replace("/tools-", "/") + self._backend_key = ( + f"{self.paths.cwd.relative_to(self.paths.root_dir).as_posix()}/terraform.tfstate".replace( + "/base-", "/" + ).replace("/tools-", "/") + ) in_container_file_path = ( - f"{self.guest_base_path}/{config_tf_file.relative_to(self.root_dir).as_posix()}" + f"{self.paths.guest_base_path}/{config_tf_file.relative_to(self.paths.root_dir).as_posix()}" ) resp = self.system_exec( "hcledit " @@ -800,7 +699,7 @@ def start(self, *arguments): def start_in_layer(self, *arguments): """Run a command that can only be performed in layer level.""" - self.check_for_layer_location() + self.paths.check_for_layer_location() return self.start(*arguments) diff --git a/leverage/containers/kubectl.py b/leverage/containers/kubectl.py index 633381dd..c8d62c74 100644 --- a/leverage/containers/kubectl.py +++ b/leverage/containers/kubectl.py @@ -4,7 +4,7 @@ from docker.types import Mount from leverage import logger -from leverage._utils import chain_commands, AwsCredsEntryPoint +from leverage._utils import chain_commands, AwsCredsEntryPoint, ExitError from leverage.container import TerraformContainer @@ -40,7 +40,7 @@ def start_shell(self): def configure(self): # make sure we are on the cluster layer - self.check_for_cluster_layer() + self.paths.check_for_cluster_layer() logger.info("Retrieving k8s cluster information...") # generate the command that will configure the new cluster @@ -61,15 +61,7 @@ def configure(self): def _get_eks_kube_config(self) -> str: exit_code, output = self._start_with_output(f"{self.TF_BINARY} output -no-color") # TODO: override on CM? if exit_code: - logger.error(output) - raise Exit(exit_code) + raise ExitError(exit_code, output) aws_eks_cmd = next(op for op in output.split("\r\n") if op.startswith("aws eks update-kubeconfig")) return aws_eks_cmd + f" --region {self.region}" - - def check_for_cluster_layer(self): - self.check_for_layer_location() - # assuming the "cluster" layer will contain the expected EKS outputs - if self.cwd.parts[-1] != "cluster": - logger.error("This command can only run at the [bold]cluster layer[/bold].") - raise Exit(1) diff --git a/leverage/leverage.py b/leverage/leverage.py index 352e3880..3aa0e236 100644 --- a/leverage/leverage.py +++ b/leverage/leverage.py @@ -8,7 +8,8 @@ from leverage.tasks import list_tasks as _list_tasks from leverage._internals import pass_state -from leverage.modules import aws, run, project, terraform, credentials, tfautomv, kubectl, shell +from leverage.modules.aws import aws +from leverage.modules import run, project, terraform, credentials, tfautomv, kubectl, shell @click.group(invoke_without_command=True) diff --git a/leverage/modules/__init__.py b/leverage/modules/__init__.py index ebb00a31..809c54ae 100644 --- a/leverage/modules/__init__.py +++ b/leverage/modules/__init__.py @@ -1,4 +1,3 @@ -from .aws import aws from .run import run from .project import project from .terraform import terraform diff --git a/leverage/modules/auth.py b/leverage/modules/auth.py new file mode 100644 index 00000000..62daf33f --- /dev/null +++ b/leverage/modules/auth.py @@ -0,0 +1,150 @@ +import time +from configparser import NoSectionError, NoOptionError +from pathlib import Path + +import boto3 +import hcl2 +from configupdater import ConfigUpdater + +from leverage import logger +from leverage._utils import key_finder, ExitError, get_or_create_section + + +class SkipProfile(Exception): + pass + + +def get_layer_profile(raw_profile: str, config_updater: ConfigUpdater, tf_profile: str, project: str): + if "local." in raw_profile: + # ignore values referencing to local variables + # we will search for profiles directly in locals.tf instead + raise SkipProfile + + # if it is exactly that variable, we already know the layer profile is tf_profile + layer_profile = tf_profile if raw_profile == "${var.profile}" else None + + # replace variables with their corresponding values + raw = raw_profile.replace("${var.profile}", tf_profile).replace("${var.project}", project) + + # the project and the role are at the beginning and end of the string + _, *account_name, _ = raw.split("-") + account_name = "-".join(account_name) + logger.info(f"Attempting to get temporary credentials for {account_name} account.") + + sso_profile = f"{project}-sso-{account_name}" + # if profile wasn't configured during configuration step + # it means we do not have permissions for the role in the account + try: + account_id = config_updater.get(f"profile {sso_profile}", "account_id").value + sso_role = config_updater.get(f"profile {sso_profile}", "role_name").value + except NoSectionError: + raise ExitError(40, f"Missing {sso_profile} permission for account {account_name}.") + + # if we are processing a profile from a different layer, we need to built it + layer_profile = layer_profile or f"{project}-{account_name}-{sso_role.lower()}" + + return account_id, account_name, sso_role, layer_profile + + +def update_config_section(updater: ConfigUpdater, layer_profile: str, data: dict): + """ + Update the section with the values given on . + """ + section = get_or_create_section(updater, layer_profile) + for key, value in data.items(): + section.set(key, value) + + updater.update_file() + + +def get_profiles(cli): + """ + Get the AWS profiles present on the layer by parsing some tf files. + """ + raw_profiles = set() + # these are files from the layer we are currently on + for name in ("config.tf", "locals.tf"): + try: + with open(name) as tf_file: + tf_config = hcl2.load(tf_file) + except FileNotFoundError: + continue + + # get all the "profile" references from the file + # but avoid lookup references (we will catch those profiles from locals.tf instead) + raw_profiles.update(set(key_finder(tf_config, "profile", "lookup"))) + + # the profile value from /config/backend.tfvars + with open(cli.paths.local_backend_tfvars) as backend_config_file: + backend_config = hcl2.load(backend_config_file) + tf_profile = backend_config["profile"] + + return tf_profile, raw_profiles + + +def refresh_layer_credentials(cli): + tf_profile, raw_profiles = get_profiles(cli) + config_updater = ConfigUpdater() + config_updater.read(cli.paths.host_aws_profiles_file) + + client = boto3.client("sso", region_name=cli.sso_region_from_main_profile) + for raw in raw_profiles: + try: + account_id, account_name, sso_role, layer_profile = get_layer_profile( + raw, + config_updater, + tf_profile, + cli.project, + ) + except SkipProfile: + continue + + # check if credentials need to be renewed + try: + expiration = int(config_updater.get(f"profile {layer_profile}", "expiration").value) / 1000 + except (NoSectionError, NoOptionError): + # first time using this profile, skip into the credential's retrieval step + logger.debug(f"No cached credentials found.") + else: + # we reduce the validity 30 minutes, to avoid expiration over long-standing tasks + renewal = time.time() + (30 * 60) + logger.debug(f"Token expiration time: {expiration}") + logger.debug(f"Token renewal time: {renewal}") + if renewal < expiration: + # still valid, nothing to do with these profile! + logger.info("Using already configured temporary credentials.") + continue + + # retrieve credentials + logger.debug(f"Retrieving role credentials for {sso_role}...") + credentials = client.get_role_credentials( + roleName=sso_role, + accountId=account_id, + accessToken=cli.get_sso_access_token(), + )["roleCredentials"] + + # update expiration on aws//config + logger.info(f"Writing {layer_profile} profile") + update_config_section( + config_updater, + f"profile {layer_profile}", + data={ + "expiration": credentials["expiration"], + }, + ) + # write credentials on aws//credentials (create the file if it doesn't exist first) + creds_path = Path(cli.paths.host_aws_credentials_file) + creds_path.touch(exist_ok=True) + credentials_updater = ConfigUpdater() + credentials_updater.read(cli.paths.host_aws_credentials_file) + + update_config_section( + credentials_updater, + layer_profile, + data={ + "aws_access_key_id": credentials["accessKeyId"], + "aws_secret_access_key": credentials["secretAccessKey"], + "aws_session_token": credentials["sessionToken"], + }, + ) + logger.info(f"Credentials for {account_name} account written successfully.") diff --git a/leverage/modules/aws.py b/leverage/modules/aws.py index b624216b..ef724b7f 100644 --- a/leverage/modules/aws.py +++ b/leverage/modules/aws.py @@ -1,16 +1,89 @@ +import boto3 import click from click.exceptions import Exit +from configupdater import ConfigUpdater from leverage import logger from leverage._internals import pass_state from leverage._internals import pass_container -from leverage.container import get_docker_client +from leverage._utils import get_or_create_section +from leverage.container import get_docker_client, SSOContainer from leverage.container import AWSCLIContainer from leverage.modules.utils import _handle_subcommand CONTEXT_SETTINGS = {"ignore_unknown_options": True} +def get_account_roles(sso_client, access_token: str) -> dict: + """ + Fetch the accounts and roles from the user. + """ + account_roles = {} + + accounts = sso_client.list_accounts(accessToken=access_token) + for account in accounts["accountList"]: + acc_role = sso_client.list_account_roles( + accessToken=access_token, + accountId=account["accountId"], + maxResults=1, # assume the first role is always the correct one + )["roleList"][0] + + account_roles[account["accountName"]] = { + "account_id": account["accountId"], + "role_name": acc_role["roleName"], + } + + return account_roles + + +def add_sso_profile( + config_updater: ConfigUpdater, section_name: str, role_name: str, account_id: str, region: str, start_url: str +): + """ + Add a profile to the config file. + """ + section = get_or_create_section(config_updater, section_name) + + data = { + "role_name": role_name, + "account_id": account_id, + "sso_region": region, + "sso_start_url": start_url, + } + for k, v in data.items(): + # can't set a dict directly, so we need to go field by field + section[k] = v + + +def configure_sso_profiles(cli: SSOContainer): + """ + Populate the ~./aws//config file with the sso profiles from the accounts. + """ + updater = ConfigUpdater() + updater.read(cli.paths.host_aws_profiles_file) + + # get values from the default profile first + default_sso_profile_name = f"profile {cli.project}-sso" + default_profile = updater[default_sso_profile_name] + region = default_profile["sso_region"].value + start_url = default_profile["sso_start_url"].value + + # then set a profile for each account + access_token = cli.get_sso_access_token() + logger.info(f"Fetching accounts and roles...") + client = boto3.client("sso", region_name=region) + account_roles = get_account_roles(client, access_token) + for acc_name, values in account_roles.items(): + # account names comes in the form of: {long project name}-{account name} + short_acc_name = acc_name.replace(cli.paths.project_long + "-", "") + section_name = f"profile {cli.project}-sso-{short_acc_name}" + logger.info(f"Adding {section_name}") + add_sso_profile(updater, section_name, values["role_name"], values["account_id"], region, start_url) + + # save/update the profile file + updater.update_file() + + @click.group(invoke_without_command=True, add_help_option=False, context_settings=CONTEXT_SETTINGS) @click.argument("args", nargs=-1, type=click.UNPROCESSED) @pass_state @@ -38,20 +111,14 @@ def configure(context, cli, args): @click.pass_context def _sso(context, cli): """configure sso""" - if ( - cli.cwd in (cli.root_dir, cli.account_dir) - or cli.account_dir.parent != cli.root_dir - or not list(cli.cwd.glob("*.tf")) - ): - logger.error("SSO configuration can only be performed at [bold]layer[/bold] level.") - raise Exit(1) + cli.paths.check_for_layer_location() # region_primary was added in refarch v1 # for v2 it was replaced by region at project level region_primary = "region_primary" - if not "region_primary" in cli.common_conf: + if "region_primary" not in cli.paths.common_conf: region_primary = "region" - default_region = cli.common_conf.get(region_primary, cli.common_conf.get("sso_region")) + default_region = cli.paths.common_conf.get(region_primary, cli.paths.common_conf.get("sso_region")) if default_region is None: logger.error("No primary region configured in global config file.") raise Exit(1) @@ -61,34 +128,34 @@ def _sso(context, cli): for key, value in default_profile.items(): cli.exec(f"configure set {key} {value}", profile="default") - if not all(sso_key in cli.common_conf for sso_key in ("sso_start_url", "sso_region")): + if not all(sso_key in cli.paths.common_conf for sso_key in ("sso_start_url", "sso_region")): logger.error("Missing configuration values for SSO in global config file.") raise Exit(1) - sso_role = cli.account_conf.get("sso_role") + sso_role = cli.paths.account_conf.get("sso_role") if not sso_role: logger.error("Missing SSO role in account config file.") raise Exit(1) - current_account = cli.account_conf.get("environment") + current_account = cli.paths.account_conf.get("environment") try: # this is for refarch v1 - account_id = cli.common_conf.get("accounts").get(current_account).get("id") + account_id = cli.paths.common_conf.get("accounts").get(current_account).get("id") except AttributeError: # this is for refarch v2 try: # this is for accounts with no org unit on top of it - account_id = cli.common_conf.get("organization").get("accounts").get(current_account).get("id") + account_id = cli.paths.common_conf.get("organization").get("accounts").get(current_account).get("id") except AttributeError: try: # this is for accounts with no org unit on top of it found = False - for ou in cli.common_conf.get("organization").get("organizational_units"): - if current_account in cli.common_conf.get("organization").get("organizational_units").get(ou).get( - "accounts" - ): + for ou in cli.paths.common_conf.get("organization").get("organizational_units"): + if current_account in cli.paths.common_conf.get("organization").get("organizational_units").get( + ou + ).get("accounts"): account_id = ( - cli.common_conf.get("organization") + cli.paths.common_conf.get("organization") .get("organizational_units") .get(ou) .get("accounts") @@ -108,8 +175,8 @@ def _sso(context, cli): logger.info(f"Configuring [bold]{cli.project}-sso[/bold] profile.") sso_profile = { - "sso_start_url": cli.common_conf.get("sso_start_url"), - "sso_region": cli.common_conf.get("sso_region", cli.common_conf.get(region_primary)), + "sso_start_url": cli.paths.common_conf.get("sso_start_url"), + "sso_region": cli.paths.common_conf.get("sso_region", cli.paths.common_conf.get(region_primary)), "sso_account_id": account_id, "sso_role_name": sso_role, } @@ -119,9 +186,7 @@ def _sso(context, cli): context.invoke(login) logger.info("Storing account information.") - exit_code = cli.system_start(cli.AWS_SSO_CONFIGURE_SCRIPT) - if exit_code: - raise Exit(exit_code) + configure_sso_profiles(cli) @aws.group(invoke_without_command=True, add_help_option=False, context_settings=CONTEXT_SETTINGS) @@ -142,7 +207,7 @@ def login(cli): # - when this cond meets: # - no account dir # - no layer dir - if not cli.get_location_type() in ["account", "layer", "layers-group"]: + if not cli.paths.get_location_type() in ["account", "layer", "layers-group"]: logger.error("SSO configuration can only be performed at [bold]layer[/bold] or [bold]account[/bold] level.") raise Exit(1) @@ -164,6 +229,6 @@ def logout(cli): raise Exit(exit_code) logger.info( - f"Don't forget to log out of your [bold]AWS SSO[/bold] start page {cli.common_conf.get('sso_start_url')}" + f"Don't forget to log out of your [bold]AWS SSO[/bold] start page {cli.paths.common_conf.get('sso_start_url')}" " and your external identity provider portal." ) diff --git a/leverage/modules/kubectl.py b/leverage/modules/kubectl.py index 431f0de6..f9c0e0f2 100644 --- a/leverage/modules/kubectl.py +++ b/leverage/modules/kubectl.py @@ -18,7 +18,7 @@ def kubectl(context, state, args): """Run Kubectl commands in a custom containerized environment.""" state.container = KubeCtlContainer(get_docker_client()) state.container.ensure_image() - state.container.check_for_layer_location() + state.container.paths.check_for_layer_location() _handle_subcommand(context=context, cli_container=state.container, args=args) diff --git a/leverage/modules/terraform.py b/leverage/modules/terraform.py index ab04de6a..e4ac10d8 100644 --- a/leverage/modules/terraform.py +++ b/leverage/modules/terraform.py @@ -9,7 +9,7 @@ from leverage import logger from leverage._internals import pass_state from leverage._internals import pass_container -from leverage._utils import tar_directory, AwsCredsContainer, LiveContainer +from leverage._utils import tar_directory, AwsCredsContainer, LiveContainer, ExitError from leverage.container import get_docker_client from leverage.container import TerraformContainer from leverage.modules.utils import env_var_option, mount_option, auth_mfa, auth_sso @@ -88,10 +88,10 @@ def init(context, tf: TerraformContainer, skip_validation, layers, args): # now change ownership on all the downloaded modules and providers for layer in layers: - tf.change_file_ownership(tf.guest_base_path / layer.relative_to(tf.root_dir) / ".terraform") + tf.change_file_ownership(tf.paths.guest_base_path / layer.relative_to(tf.paths.root_dir) / ".terraform") # and then providers in the cache folder - if tf.tf_cache_dir: - tf.change_file_ownership(tf.tf_cache_dir) + if tf.paths.tf_cache_dir: + tf.change_file_ownership(tf.paths.tf_cache_dir) @terraform.command(context_settings=CONTEXT_SETTINGS) @@ -200,7 +200,7 @@ def _import(tf, address, _id): @pass_container def refresh_credentials(tf): """Refresh the AWS credentials used on the current layer.""" - tf.check_for_layer_location() + tf.paths.check_for_layer_location() if exit_code := tf.refresh_credentials(): raise Exit(exit_code) @@ -223,34 +223,32 @@ def invoke_for_all_commands(tf, layers, command, args, skip_validation=True): layers = layers.split(",") if len(layers) > 0 else [] # based on the location type manage the layers parameter - if tf.get_location_type() == "layer" and len(layers) == 0: + location_type = tf.paths.get_location_type() + if location_type == "layer" and len(layers) == 0: # running on a layer - layers = [tf.cwd] - elif tf.get_location_type() == "layer": + layers = [tf.paths.cwd] + elif location_type == "layer": # running on a layer but --layers was set - logger.error("Can not set [bold]--layers[/bold] inside a layer.") - raise Exit(1) - elif tf.get_location_type() in ["account", "layers-group"] and len(layers) == 0: + raise ExitError(1, "Can not set [bold]--layers[/bold] inside a layer.") + elif location_type in ["account", "layers-group"] and len(layers) == 0: # running on an account but --layers was not set - logger.error("[bold]--layers[/bold] has to be set.") - raise Exit(1) - elif not tf.get_location_type() in ["account", "layer", "layers-group"]: + raise ExitError(1, "[bold]--layers[/bold] has to be set.") + elif location_type not in ["account", "layer", "layers-group"]: # running outside a layer and account - logger.error("This command has to be run inside a layer or account directory.") - raise Exit(1) + raise ExitError(1, "This command has to be run inside a layer or account directory.") else: # running on an account with --layers set - layers = [tf.cwd / x for x in layers] + layers = [tf.paths.cwd / x for x in layers] # get current location - original_location = tf.cwd + original_location = tf.paths.cwd original_working_dir = tf.container_config["working_dir"] # validate each layer before calling the execute command for layer in layers: logger.debug(f"Checking for layer {layer}...") # change to current dir and set it in the container - tf.cwd = layer + tf.paths.cwd = layer # check layers existence if not layer.is_dir(): @@ -264,7 +262,7 @@ def invoke_for_all_commands(tf, layers, command, args, skip_validation=True): validate_for_all_commands(layer, skip_validation=skip_validation) # change to original dir and set it in the container - tf.cwd = original_location + tf.paths.cwd = original_location # check layers existence for layer in layers: @@ -272,16 +270,17 @@ def invoke_for_all_commands(tf, layers, command, args, skip_validation=True): logger.info(f"Invoking command for layer {layer}...") # change to current dir and set it in the container - tf.cwd = layer + tf.paths.cwd = layer # set the working dir - tf.container_config["working_dir"] = f"{tf.guest_base_path}/{tf.cwd.relative_to(tf.root_dir).as_posix()}" + working_dir = f"{tf.paths.guest_base_path}/{tf.paths.cwd.relative_to(tf.paths.root_dir).as_posix()}" + tf.container_config["working_dir"] = working_dir # execute the actual command command(args=args) # change to original dir and set it in the container - tf.cwd = original_location + tf.paths.cwd = original_location # change to original workgindir tf.container_config["working_dir"] = original_working_dir @@ -320,15 +319,15 @@ def _init(tf, args): for index, arg in enumerate(args) if not arg.startswith("-backend-config") or not arg[index - 1] == "-backend-config" ] - args.append(f"-backend-config={tf.backend_tfvars}") + args.append(f"-backend-config={tf.paths.backend_tfvars}") - tf.check_for_layer_location() + tf.paths.check_for_layer_location() with LiveContainer(tf) as container: # create the .ssh directory container.exec_run("mkdir -p /root/.ssh") # copy the entire ~/.ssh/ folder - tar_bytes = tar_directory(tf.home / ".ssh") + tar_bytes = tar_directory(tf.paths.home / ".ssh") # into /root/.ssh container.put_archive("/root/.ssh/", tar_bytes) # correct the owner of the files to match with the docker internal user @@ -454,11 +453,11 @@ def _make_layer_backend_key(cwd, account_dir, account_name): @pass_container -def _validate_layout(tf): - tf.check_for_layer_location() +def _validate_layout(tf: TerraformContainer): + tf.paths.check_for_layer_location() # Check for `environment = ` in account.tfvars - account_name = tf.account_conf.get("environment") + account_name = tf.paths.account_conf.get("environment") logger.info("Checking environment name definition in [bold]account.tfvars[/bold]...") if account_name is None: logger.error("[red]✘ FAILED[/red]\n") @@ -466,10 +465,10 @@ def _validate_layout(tf): logger.info("[green]✔ OK[/green]\n") # Check if account directory name matches with environment name - if tf.account_dir.stem != account_name: + if tf.paths.account_dir.stem != account_name: logger.warning( "[yellow]‼[/yellow] Account directory name does not match environment name.\n" - f" Expected [bold]{account_name}[/bold], found [bold]{tf.account_dir.stem}[/bold]\n" + f" Expected [bold]{account_name}[/bold], found [bold]{tf.paths.account_dir.stem}[/bold]\n" ) backend_key = tf.backend_key.split("/") @@ -478,7 +477,7 @@ def _validate_layout(tf): valid_layout = True # Check backend bucket key - expected_backend_keys = _make_layer_backend_key(tf.cwd, tf.account_dir, account_name) + expected_backend_keys = _make_layer_backend_key(tf.paths.cwd, tf.paths.account_dir, account_name) logger.info("Checking backend key...") logger.info(f"Found: '{'/'.join(backend_key)}'") backend_key = backend_key[:-1] @@ -491,7 +490,7 @@ def _validate_layout(tf): logger.error("[red]✘ FAILED[/red]\n") valid_layout = False - backend_tfvars = tf.account_config_dir / tf.BACKEND_TFVARS + backend_tfvars = tf.paths.account_config_dir / tf.paths.BACKEND_TF_VARS # TODO use paths.backend_tfvars instead? backend_tfvars = hcl2.loads(backend_tfvars.read_text()) if backend_tfvars.exists() else {} logger.info("Checking [bold]backend.tfvars[/bold]:\n") diff --git a/leverage/path.py b/leverage/path.py index e4e70226..a7004a81 100644 --- a/leverage/path.py +++ b/leverage/path.py @@ -1,11 +1,16 @@ """ Utilities to obtain relevant files' and directories' locations """ +import os from pathlib import Path from subprocess import run from subprocess import PIPE from subprocess import CalledProcessError +import hcl2 + +from leverage._utils import ExitError + class NotARepositoryError(RuntimeError): pass @@ -120,3 +125,152 @@ def get_build_script_path(filename="build.py"): break cur_path = cur_path.parent + + +class PathsHandler: + COMMON_TF_VARS = "common.tfvars" + ACCOUNT_TF_VARS = "account.tfvars" + BACKEND_TF_VARS = "backend.tfvars" + + def __init__(self, env_conf): + self.home = Path.home() + self.cwd = Path.cwd() + try: + # TODO: just call get_root_path once and use it to initiate the rest of variables? + self.root_dir = Path(get_root_path()) + self.account_dir = Path(get_account_path()) + self.common_config_dir = Path(get_global_config_path()) + self.account_config_dir = Path(get_account_config_path()) + except NotARepositoryError: + raise ExitError(1, "Out of Leverage project context. Please cd into a Leverage project directory first.") + + # TODO: move the confs into a Config class + common_config = self.common_config_dir / self.COMMON_TF_VARS + self.common_conf = hcl2.loads(common_config.read_text()) if common_config.exists() else {} + + account_config = self.account_config_dir / self.ACCOUNT_TF_VARS + self.account_conf = hcl2.loads(account_config.read_text()) if account_config.exists() else {} + + # Get project name + self.project = self.common_conf.get("project", env_conf.get("PROJECT", False)) + if not self.project: + raise ExitError(1, "Project name has not been set. Exiting.") + + # Project mount location + self.project_long = self.common_conf.get("project_long", "project") + self.guest_base_path = f"/{self.project_long}" + + # Ensure credentials directory + self.host_aws_credentials_dir = self.home / ".aws" / self.project + if not self.host_aws_credentials_dir.exists(): + self.host_aws_credentials_dir.mkdir(parents=True) + self.sso_cache = self.host_aws_credentials_dir / "sso" / "cache" + + @property + def guest_account_base_path(self): + return f"{self.guest_base_path}/{self.account_dir.relative_to(self.root_dir).as_posix()}" + + @property + def common_tfvars(self): + return f"{self.guest_base_path}/config/{self.COMMON_TF_VARS}" + + @property + def account_tfvars(self): + return f"{self.guest_account_base_path}/config/{self.ACCOUNT_TF_VARS}" + + @property + def backend_tfvars(self): + return f"{self.guest_account_base_path}/config/{self.BACKEND_TF_VARS}" + + @property + def guest_aws_credentials_dir(self): + return f"/root/tmp/{self.project}" + + @property + def host_aws_profiles_file(self): + return f"{self.host_aws_credentials_dir}/config" + + @property + def host_aws_credentials_file(self): + return self.host_aws_credentials_dir / "credentials" + + @property + def local_backend_tfvars(self): + return self.account_config_dir / self.BACKEND_TF_VARS + + @property + def sso_token_file(self): + return f"{self.sso_cache}/token" + + def get_location_type(self): + """ + Returns the location type: + - root + - account + - config + - layer + - sublayer + - not a project + """ + if self.cwd == self.root_dir: + return "root" + elif self.cwd == self.account_dir: + return "account" + elif self.cwd in (self.common_config_dir, self.account_config_dir): + return "config" + elif (self.cwd.as_posix().find(self.account_dir.as_posix()) >= 0) and list(self.cwd.glob("*.tf")): + return "layer" + elif (self.cwd.as_posix().find(self.account_dir.as_posix()) >= 0) and not list(self.cwd.glob("*.tf")): + return "layers-group" + else: + return "not a project" + + def assert_running_leverage_project(self): + if self.root_dir == self.account_dir == self.common_config_dir == self.account_config_dir == self.cwd: + raise ExitError(1, "Not running in a Leverage project. Exiting.") + + def guest_config_file(self, file): + """Map config file in host to location in guest. + + Args: + file (pathlib.Path): File in host to map + + Raises: + Exit: If file is not contained in any valid config directory + + Returns: + str: Path in guest to config file + """ + file_name = file.name + + if file.parent == self.account_config_dir: + return f"{self.guest_account_base_path}/config/{file_name}" + if file.parent == self.common_config_dir: + return f"{self.guest_base_path}/config/{file_name}" + + raise ExitError(1, "File is not part of any config directory.") + + @property + def tf_cache_dir(self): + return os.getenv("TF_PLUGIN_CACHE_DIR") + + def check_for_layer_location(self): + """Make sure the command is being ran at layer level. If not, bail.""" + if self.cwd in (self.common_config_dir, self.account_config_dir): + raise ExitError(1, "Currently in a configuration directory, no Terraform command can be run here.") + + if self.cwd in (self.root_dir, self.account_dir): + raise ExitError( + 1, + "Terraform commands cannot run neither in the root of the project or in" + " the root directory of an account.", + ) + + if not list(self.cwd.glob("*.tf")): + raise ExitError(1, "This command can only run at [bold]layer[/bold] level.") + + def check_for_cluster_layer(self): + self.check_for_layer_location() + # assuming the "cluster" layer will contain the expected EKS outputs + if self.cwd.parts[-1] != "cluster": + raise ExitError(1, "This command can only run at the [bold]cluster layer[/bold].") diff --git a/setup.cfg b/setup.cfg index 5f15aeff..4620b7ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,8 @@ install_requires = dockerpty == 0.4.1 questionary == 1.10.0 python-hcl2 == 3.0.1 + boto3 == 1.33.2 + configupdater == 3.2 include_package_data = True [options.entry_points] diff --git a/tests/test_containers/test_kubectl.py b/tests/test_containers/test_kubectl.py index 49a83477..8435e4ee 100644 --- a/tests/test_containers/test_kubectl.py +++ b/tests/test_containers/test_kubectl.py @@ -4,8 +4,8 @@ import pytest from click.exceptions import Exit -from leverage.container import TerraformContainer from leverage.containers.kubectl import KubeCtlContainer +from leverage.path import PathsHandler from tests.test_containers import container_fixture_factory AWS_EKS_UPDATE_KUBECONFIG = "aws eks update-kubeconfig --name test-cluster --profile test-profile --region us-east-1" @@ -24,7 +24,7 @@ def kubectl_container(muted_click_context): def test_get_eks_kube_config(kubectl_container): tf_output = "\r\naws eks update-kubeconfig --name test-cluster --profile test-profile\r\n" with patch.object(kubectl_container, "_start_with_output", return_value=(0, tf_output)): - kubectl_container.cwd = Path("/project/account/us-east-1/cluster") + kubectl_container.paths.cwd = Path("/project/account/us-east-1/cluster") cmd = kubectl_container._get_eks_kube_config() assert cmd == AWS_EKS_UPDATE_KUBECONFIG @@ -39,18 +39,6 @@ def test_get_eks_kube_config_tf_output_error(kubectl_container): kubectl_container._get_eks_kube_config() -def test_check_for_cluster_layer(kubectl_container, propagate_logs, caplog): - """ - Test that if we are not on a cluster layer, we raise an error. - """ - with patch.object(TerraformContainer, "check_for_layer_location"): # assume parent method is already tested - with pytest.raises(Exit): - kubectl_container.cwd = Path("/random") - kubectl_container.check_for_cluster_layer() - - assert caplog.messages[0] == "This command can only run at the [bold]cluster layer[/bold]." - - ################# # test commands # ################# @@ -80,7 +68,7 @@ def test_start_shell(kubectl_container): # don't rely on the filesystem -@patch.object(KubeCtlContainer, "check_for_cluster_layer", Mock()) +@patch.object(PathsHandler, "check_for_cluster_layer", Mock()) # nor terraform @patch.object(KubeCtlContainer, "_get_eks_kube_config", Mock(return_value=AWS_EKS_UPDATE_KUBECONFIG)) def test_configure(kubectl_container, fake_os_user): @@ -115,18 +103,19 @@ def test_start_shell_mfa(kubectl_container): assert container_args["environment"]["AWS_SHARED_CREDENTIALS_FILE"] == "/root/.aws/test/credentials" -def test_start_shell_sso(kubectl_container): +@patch("leverage.container.refresh_layer_credentials") +def test_start_shell_sso(mock_refresh, kubectl_container): """ - Make sure the command is executed through the proper SSO script. + Make sure the SSO flag is set properly before the command. """ kubectl_container.enable_sso() kubectl_container._check_sso_token = Mock(return_value=True) kubectl_container.start_shell() container_args = kubectl_container.client.api.create_container.call_args_list[0][1] - # we want a shell, so -> /bin/bash with no entrypoint + # we want a shell, so -> /bin/bash and refresh_sso_credentials flag assert container_args["command"] == "/bin/bash" - assert container_args["entrypoint"] == "/root/scripts/aws-sso/aws-sso-entrypoint.sh -- " + assert mock_refresh.called_once # make sure we are pointing to the right AWS credentials: /tmp/ folder for SSO assert container_args["environment"]["AWS_CONFIG_FILE"] == "/root/tmp/test/config" diff --git a/tests/test_containers/test_terraform.py b/tests/test_containers/test_terraform.py index 7bd8939e..4c91e2de 100644 --- a/tests/test_containers/test_terraform.py +++ b/tests/test_containers/test_terraform.py @@ -1,3 +1,5 @@ +from unittest import mock + import pytest from leverage.container import TerraformContainer @@ -26,12 +28,34 @@ def test_tf_plugin_cache_dir(terraform_container): assert next(m for m in container_args["host_config"]["Mounts"] if m["Target"] == "/home/testing/.terraform/cache") -def test_refresh_credentials(terraform_container): +@mock.patch("leverage.container.refresh_layer_credentials") +def test_refresh_credentials(mock_refresh, terraform_container): terraform_container.enable_sso() terraform_container.refresh_credentials() container_args = terraform_container.client.api.create_container.call_args_list[0][1] - # we want a shell, so -> /bin/bash with no entrypoint + # we want a shell, so -> /bin/bash and refresh_sso_credentials flag assert container_args["command"] == 'echo "Done."' - # import ipdb; ipdb.set_trace() - assert container_args["entrypoint"] == "/root/scripts/aws-sso/aws-sso-entrypoint.sh -- " + assert mock_refresh.called_once + + +@mock.patch("leverage.container.refresh_layer_credentials") +def test_auth_method_sso_enabled(mock_refresh, terraform_container): + terraform_container.sso_enabled = True + terraform_container.auth_method() + + assert mock_refresh.called_once + + +def test_auth_method_mfa_enabled(terraform_container): + terraform_container.sso_enabled = False + terraform_container.mfa_enabled = True + + assert terraform_container.auth_method() == "/root/scripts/aws-mfa/aws-mfa-entrypoint.sh -- " + + +def test_auth_method_else(terraform_container): + terraform_container.sso_enabled = False + terraform_container.mfa_enabled = False + + assert terraform_container.auth_method() == "" diff --git a/tests/test_modules/test_auth.py b/tests/test_modules/test_auth.py new file mode 100644 index 00000000..b295d954 --- /dev/null +++ b/tests/test_modules/test_auth.py @@ -0,0 +1,267 @@ +from collections import namedtuple +from unittest import mock +from unittest.mock import Mock, MagicMock, PropertyMock + +import pytest +from configupdater import ConfigUpdater + +from leverage._utils import ExitError +from leverage.container import SSOContainer +from leverage.modules.auth import refresh_layer_credentials, get_layer_profile, SkipProfile +from leverage.modules.aws import get_account_roles, add_sso_profile, configure_sso_profiles +from tests.test_containers import container_fixture_factory + + +@pytest.fixture +def sso_container(with_click_context, propagate_logs): + mocked_cont = container_fixture_factory(SSOContainer) + mocked_cont.get_sso_access_token = Mock(return_value="testing-token") + + # mock PathsHandler with a named tuple? + with mock.patch( + "leverage.container.PathsHandler.local_backend_tfvars", + new_callable=PropertyMock(return_value="~/config/backend.tfvars"), + ), mock.patch( + "leverage.container.PathsHandler.host_aws_profiles_file", + new_callable=PropertyMock(return_value="~/.aws/test/config"), + ), mock.patch( + "leverage.container.PathsHandler.host_aws_credentials_file", + new_callable=PropertyMock(return_value="~/.aws/test/credentials"), + ): + yield mocked_cont + + +ACC_ROLES = { + "accName1": { + "account_id": "accId1", + "role_name": "devops", + }, + "accName2": { + "account_id": "accId2", + "role_name": "devops", + }, +} + + +def test_get_account_roles(): + sso_client = Mock() + sso_client.list_accounts = Mock( + return_value={ + "accountList": [ + { + "accountId": "accId1", + "accountName": "accName1", + }, + { + "accountId": "accId2", + "accountName": "accName2", + }, + ] + } + ) + sso_client.list_account_roles = Mock(return_value={"roleList": [{"roleName": "devops"}]}) + + assert get_account_roles(sso_client, "token-123") == ACC_ROLES + + +def test_add_sso_profile(): + mocked_section = MagicMock() + add_sso_profile(mocked_section, "section_1", "role_1", "acc_id_1", "us-east-1", "https://test.awsapps.com/start") + + assert mocked_section.get_section.mock_calls[1].args == ("role_name", "role_1") + assert mocked_section.get_section.mock_calls[2].args == ("account_id", "acc_id_1") + assert mocked_section.get_section.mock_calls[3].args == ("sso_region", "us-east-1") + assert mocked_section.get_section.mock_calls[4].args == ("sso_start_url", "https://test.awsapps.com/start") + + +UpdaterAttr = namedtuple("UpdaterAttr", ["value"]) +mocked_updater = MagicMock() +mocked_updater.__getitem__.return_value = { + "sso_region": UpdaterAttr(value="us-test-1"), + "sso_start_url": UpdaterAttr(value="https://test.awsapps.com/start"), +} + + +@mock.patch("boto3.client") +def test_configure_sso_profiles(mocked_boto, sso_container): + with mock.patch("leverage.modules.aws.ConfigUpdater.__new__", return_value=mocked_updater): + with mock.patch("leverage.modules.aws.get_account_roles", return_value=ACC_ROLES): + with mock.patch("leverage.modules.aws.add_sso_profile") as mocked_add_profile: + configure_sso_profiles(sso_container) + + # 2 profiles were added + assert mocked_add_profile.call_args_list[0].args[1:] == ( + "profile test-sso-accName1", + "devops", + "accId1", + "us-test-1", + "https://test.awsapps.com/start", + ) + assert mocked_add_profile.call_args_list[1].args[1:] == ( + "profile test-sso-accName2", + "devops", + "accId2", + "us-test-1", + "https://test.awsapps.com/start", + ) + # and the file was saved + assert mocked_updater.update_file.called + + +@pytest.mark.parametrize("profile", ["local.account.profile", "${local.profile}-test-devops"]) +def test_get_layer_profile_skip_profile(profile): + with pytest.raises(SkipProfile): + get_layer_profile(profile, Mock(), "DevOps", "project") + + +def test_get_layer_profile_no_section_error(muted_click_context): + updater = ConfigUpdater() # empty config -> no sections + with pytest.raises(ExitError, match="Missing project-sso-acc123 permission for account acc123."): + get_layer_profile("project-acc123-devops", updater, "DevOps", "project") + + +def test_get_layer_profile(muted_click_context): + updater = ConfigUpdater() + updater_values = [ + UpdaterAttr(value="123"), # first call: account + UpdaterAttr(value="devops"), # second call: role + ] + + with mock.patch.object(updater, "get", side_effect=updater_values): + acc_id, acc_name, sso_role, layer_profile = get_layer_profile( + "project-acc123-devops", updater, "DevOps", "project" + ) + + assert acc_id == "123" + assert acc_name == "acc123" + assert sso_role == "devops" + assert layer_profile == "project-acc123-devops" + + +NOW_EPOCH = 170500000 + +FILE_CONFIG_TF = """ +provider "aws" { + region = var.region + profile = var.profile +} +""" + +FILE_LOCALS_TF = """ +provider "aws" { + region = var.region + profile = var.profile +} +""" + +FILE_BACKEND_TFVARS = """ +profile = "test-apps-devstg-devops" +""" + +FILE_AWS_CONFIG = """ +[profile test-sso] +sso_region = us-test-1 + +[profile test-sso-apps-devstg] +account_id = 123 +role_name = devops + +[profile test-apps-devstg-devops] +expiration=1705859470 + +[profile test-sso-first] +account_id = 456 +role_name = devops + +[profile test-sso-valid] +account_id = 789 +role_name = devops + +[profile test-valid-devops] +expiration=170600900000 +""" + +FILE_AWS_CREDENTIALS = """ +[test-apps-devstg-devops] +aws_access_key_id = access-key +aws_secret_access_key = secret-key +aws_session_token = session-token +""" + +data_dict = { + "config.tf": FILE_CONFIG_TF, + "locals.tf": FILE_LOCALS_TF, + "~/config/backend.tfvars": FILE_BACKEND_TFVARS, + "~/.aws/test/config": FILE_AWS_CONFIG, + "~/.aws/test/credentials": FILE_AWS_CREDENTIALS, +} + + +def open_side_effect(name, *args, **kwargs): + """ + Everytime we call open(), this side effect will try to get the value from data_dict rather than reading a disk file. + """ + return mock.mock_open(read_data=data_dict[name])() + + +b3_client = Mock() +b3_client.get_role_credentials = Mock( + return_value={ + "roleCredentials": { + "expiration": "1705859400", + "accessKeyId": "access-key", + "secretAccessKey": "secret-key", + "sessionToken": "session-token", + } + } +) + + +@mock.patch("leverage.modules.auth.get_profiles", new=Mock(return_value=("test-first-devops", ["test-first-profile"]))) +@mock.patch("leverage.modules.auth.get_or_create_section", new=Mock()) +@mock.patch("leverage.modules.aws.ConfigUpdater.update_file", new=Mock()) +@mock.patch("builtins.open", side_effect=open_side_effect) +@mock.patch("boto3.client", return_value=b3_client) +@mock.patch("pathlib.Path.touch", new=Mock()) +def test_refresh_layer_credentials_first_time(mock_open, mock_boto, sso_container, caplog): + refresh_layer_credentials(sso_container) + + # there was no previous profile set for the layer + assert caplog.messages[1] == "No cached credentials found." + # so we retrieve it + assert caplog.messages[2] == "Retrieving role credentials for devops..." + + +@mock.patch("leverage.modules.auth.get_profiles", new=Mock(return_value=("test-valid-devops", ["test-valid-profile"]))) +@mock.patch("leverage.modules.auth.get_or_create_section", new=Mock()) +@mock.patch("leverage.modules.aws.ConfigUpdater.update_file", new=Mock()) +@mock.patch("builtins.open", side_effect=open_side_effect) +@mock.patch("boto3.client", return_value=b3_client) +@mock.patch("time.time", new=Mock(return_value=NOW_EPOCH)) +def test_refresh_layer_credentials_still_valid(mock_open, mock_boto, sso_container, caplog): + refresh_layer_credentials(sso_container) + + assert caplog.messages[1] == f"Token expiration time: 170600900.0" + assert caplog.messages[2] == f"Token renewal time: 170501800" # NOW_EPOCH - 30*60 + # renewal is less than expiration, so our credentials are still fine + assert caplog.messages[3] == "Using already configured temporary credentials." + + +@mock.patch("leverage.modules.auth.update_config_section") +@mock.patch("builtins.open", side_effect=open_side_effect) +@mock.patch("boto3.client", return_value=b3_client) +@mock.patch("time.time", new=Mock(return_value=1705859000)) +@mock.patch("pathlib.Path.touch", new=Mock()) +def test_refresh_layer_credentials(mock_boto, mock_open, mock_update_conf, sso_container, propagate_logs): + refresh_layer_credentials(sso_container) + + # the expiration was set + assert mock_update_conf.call_args_list[0].args[1] == "profile test-apps-devstg-devops" + assert mock_update_conf.call_args_list[0].kwargs["data"] == {"expiration": "1705859400"} + # and the corresponding attributes + assert mock_update_conf.call_args_list[1].args[1] == "test-apps-devstg-devops" + assert mock_update_conf.call_args_list[1].kwargs["data"] == { + "aws_access_key_id": "access-key", + "aws_secret_access_key": "secret-key", + "aws_session_token": "session-token", + } diff --git a/tests/test_modules/test_terraform.py b/tests/test_modules/test_terraform.py index 5c530198..d8030bcb 100644 --- a/tests/test_modules/test_terraform.py +++ b/tests/test_modules/test_terraform.py @@ -21,7 +21,7 @@ def terraform_container(muted_click_context): ctx.obj = state # assume we are on a valid location - with patch.object(TerraformContainer, "check_for_layer_location", Mock()): + with patch.object(tf_container.paths, "check_for_layer_location", Mock()): # assume we have valid credentials with patch.object(AwsCredsContainer, "__enter__", Mock()): yield tf_container diff --git a/tests/test_path.py b/tests/test_path.py index 9c09df40..d1f030c5 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -1,14 +1,19 @@ from pathlib import Path +from unittest.mock import patch import pytest from leverage import path as lepath -from leverage.path import get_home_path -from leverage.path import get_working_path -from leverage.path import get_account_path -from leverage.path import get_build_script_path -from leverage.path import get_global_config_path -from leverage.path import get_account_config_path +from leverage._utils import ExitError +from leverage.path import ( + get_home_path, + PathsHandler, + get_working_path, + get_account_config_path, + get_global_config_path, + get_build_script_path, + get_account_path, +) def test_get_working_path(): @@ -100,3 +105,16 @@ def test_get_build_script_path(dir_structure, build_script_location): def test_get_build_script_path_no_build_script(dir_structure): assert get_build_script_path() is None + + +def test_check_for_cluster_layer(muted_click_context, propagate_logs, caplog): + """ + Test that if we are not on a cluster layer, we raise an error. + """ + paths = PathsHandler({"PROJECT": "test"}) + with patch.object(paths, "check_for_layer_location"): # assume parent method is already tested + with pytest.raises(ExitError): + paths.cwd = Path("/random") + paths.check_for_cluster_layer() + + assert caplog.messages[0] == "This command can only run at the [bold]cluster layer[/bold]." diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..719d60a0 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,86 @@ +from leverage._utils import key_finder + + +def test_key_finder_real_scenario(): + data = { + "provider": [ + { + "aws": {"region": "${var.region}", "profile": "${var.profile}"}, + }, + ], + "terraform": [ + { + "required_version": ">= 1.2.7", + "required_providers": [{"aws": "~> 4.10", "sops": {"source": "carlpett/sops", "version": "~> 0.7"}}], + "backend": [{"s3": {"key": "apps-devstg/notifications/terraform.tfstate"}}], + } + ], + "data": [ + { + "terraform_remote_state": { + "keys": { + "backend": "s3", + "config": { + "region": "${var.region}", + "profile": "${var.profile}", + "bucket": "${var.bucket}", + "key": "${var.environment}/security-keys/terraform.tfstate", + }, + } + } + }, + { + "sops_file": {"secrets": {"source_file": "secrets.enc.yaml"}}, + }, + ], + } + found = key_finder(data, "profile") + + assert found == ["${var.profile}", "${var.profile}"] + + +def test_key_finder_simple_scenario(): + data = { + "test1": { + "a": {"profile": "1a"}, + "b": {"profile": "1b"}, + }, + "test2": {"profile": "2"}, + "test3": {"profile": "3"}, + } + found = key_finder(data, "profile") + + assert found == ["1a", "1b", "2", "3"] + + +def test_key_finder_skip_non_dict_values_in_lists(): + data = { + "test1": { + "a": {"profile": "1a"}, + "b": {"wrong": ["1b", "1c", "1d"]}, + }, + } + found = key_finder(data, "profile") + + assert found == ["1a"] + + +def test_key_finder_avoid_lookup(): + data = { + "terraform_remote_state": { + "shared-vpcs": { + "for_each": "${local.shared-vpcs}", + "backend": "s3", + "config": { + "region": '${lookup(each.value,"region")}', + "profile": '${lookup(each.value,"profile")}', + "bucket": '${lookup(each.value,"bucket")}', + "key": '${lookup(each.value,"key")}', + }, + } + }, + "profile": "valid", + } + found = key_finder(data, "profile", avoid="lookup") + + assert found == ["valid"]