From 479404b679b8fba303d4de2fb196d00a9f6bde5a Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Mon, 3 Nov 2025 11:57:42 +0100 Subject: [PATCH 1/2] Add Python 3.14 Fedora 43 container --- Makefile | 2 +- README.md | 2 + examples/pipenv-test-app/Pipfile | 2 +- examples/pipenv-test-app/Pipfile.lock | 273 ++++++++++++--------- specs/multispec.yml | 40 +++ src/test/pipenv-test-app/3.14/Pipfile.lock | 242 ++++++++++++++++++ 6 files changed, 438 insertions(+), 123 deletions(-) create mode 100644 src/test/pipenv-test-app/3.14/Pipfile.lock diff --git a/Makefile b/Makefile index f9de5e23..434f35e0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Variables are documented in common/build.sh. BASE_IMAGE_NAME = python -VERSIONS = 3.6 3.9 3.9-minimal 3.11 3.11-minimal 3.12 3.12-minimal 3.13 3.13-minimal +VERSIONS = 3.6 3.9 3.9-minimal 3.11 3.11-minimal 3.12 3.12-minimal 3.13 3.13-minimal 3.14 3.14-minimal OPENSHIFT_NAMESPACES = DOCKER_BUILD_CONTEXT = .. diff --git a/README.md b/README.md index 9adf9537..12550ef0 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ Table start |3.12-minimal|
`quay.io/sclorg/python-312-minimal-c9s`
|
`quay.io/sclorg/python-312-minimal-c10s`
||
`registry.redhat.io/rhel8/python-312-minimal`
|
`registry.redhat.io/rhel9/python-312-minimal`
|
`registry.redhat.io/rhel10/python-312-minimal`
| |3.13||
`quay.io/sclorg/python-313-c10s`
|
`quay.io/fedora/python-313`
|||| |3.13-minimal||
`quay.io/sclorg/python-313-minimal-c10s`
|
`quay.io/fedora/python-313-minimal`
|||| +|3.14|||
`quay.io/fedora/python-314`
|||| +|3.14-minimal|||
`quay.io/fedora/python-314-minimal`
|||| diff --git a/examples/pipenv-test-app/Pipfile b/examples/pipenv-test-app/Pipfile index fc4d0c80..45aee705 100644 --- a/examples/pipenv-test-app/Pipfile +++ b/examples/pipenv-test-app/Pipfile @@ -12,4 +12,4 @@ setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} pytest = ">=2.8.0" [requires] -python_version = "3.13" +python_version = "3.14" diff --git a/examples/pipenv-test-app/Pipfile.lock b/examples/pipenv-test-app/Pipfile.lock index de1ccaf4..11c134ec 100644 --- a/examples/pipenv-test-app/Pipfile.lock +++ b/examples/pipenv-test-app/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "1c042fcc7ce0692ac9ad83e0cc00f01a81991e88b45c33d6edc3e55ac79ae443" + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" }, "pipfile-spec": 6, "requires": { - "python_version": "3.13" + "python_version": "3.14" }, "sources": [ { @@ -18,107 +18,130 @@ "default": { "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" ], - "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" }, "charset-normalizer": { "hashes": [ - "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" + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "markers": "python_version >= '3.7'", + "version": "==3.4.4" }, "gunicorn": { "hashes": [ @@ -130,19 +153,19 @@ }, "idna": { "hashes": [ - "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" ], - "markers": "python_version >= '3.6'", - "version": "==3.10" + "markers": "python_version >= '3.8'", + "version": "==3.11" }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==25.0" }, "requests": { "hashes": [ @@ -150,6 +173,7 @@ "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" ], "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.28.2" }, "setuptools": { @@ -157,13 +181,11 @@ "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" ], - "index": "pypi", "markers": "python_version >= '3.12'", "version": "==67.7.2" }, "testapp": { - "editable": true, - "path": "." + "version": "==0.1" }, "urllib3": { "hashes": [ @@ -177,35 +199,44 @@ "develop": { "iniconfig": { "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" + "markers": "python_version >= '3.10'", + "version": "==2.3.0" }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==25.0" }, "pluggy": { "hashes": [ - "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", - "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], "markers": "python_version >= '3.8'", - "version": "==1.5.0" + "version": "==2.19.2" }, "pytest": { "hashes": [ - "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", - "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" ], "index": "pypi", - "version": "==8.3.3" + "markers": "python_version >= '3.10'", + "version": "==9.0.2" } } } diff --git a/specs/multispec.yml b/specs/multispec.yml index 68a2cc18..395dc1e2 100644 --- a/specs/multispec.yml +++ b/specs/multispec.yml @@ -121,7 +121,41 @@ specs: 'libffi-devel', 'libtool-ltdl', 'enchant', 'redhat-rpm-config', 'krb5-devel'] + fedora43: + distros: + - fedora-43-x86_64 + fedora_version: "43" + minimal_image: "quay.io/fedora/fedora-minimal:43" + s2i_base: quay.io/fedora/s2i-base + img_tag: "43" + org: "fedora" + python_pkgs: ['python3', 'python3-devel', 'python3-setuptools', 'python3-pip'] + base_pkgs: ['nss_wrapper-libs', 'httpd', 'httpd-devel', 'atlas-devel', 'gcc-gfortran', + 'libffi-devel', 'libtool-ltdl', 'enchant', 'redhat-rpm-config', + 'krb5-devel'] + version: + "3.14-minimal": + version: "3.14" + short_ver: "314" + minimal: true + base_img_version: "1" + python_img_version: "1" + # Python 3.14 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.14" + main_image: "quay.io/fedora/python-314-minimal" + + "3.14": + version: "3.14" + short_ver: "314" + minimal: false + base_img_version: "1" + # Python 3.14 doesn't come as a module + module_stream: "" + pkg_prefix: "python3.14" + main_image: "quay.io/fedora/python-314" + "3.13-minimal": version: "3.13" short_ver: "313" @@ -252,3 +286,9 @@ matrix: - fedora-42-x86_64 - centos-stream-10-x86_64 version: "3.13-minimal" + - distros: + - fedora-43-x86_64 + version: "3.14" + - distros: + - fedora-43-x86_64 + version: "3.14-minimal" diff --git a/src/test/pipenv-test-app/3.14/Pipfile.lock b/src/test/pipenv-test-app/3.14/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/src/test/pipenv-test-app/3.14/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "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.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} From ca9be4c8b86943f2a839596484d241971c194788 Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Mon, 5 Jan 2026 13:12:48 +0100 Subject: [PATCH 2/2] Distgen generated content --- 3.14-minimal/Dockerfile.fedora | 104 +++++ 3.14-minimal/README.md | 123 ++++++ .../opt/app-root/etc/generate_container_user | 19 + 3.14-minimal/root/opt/wheels | 1 + 3.14-minimal/root/usr/bin/cgroup-limits | 192 +++++++++ 3.14-minimal/root/usr/bin/fix-permissions | 27 ++ .../root/usr/bin/rpm-file-permissions | 21 + 3.14-minimal/s2i/bin/assemble | 134 ++++++ 3.14-minimal/s2i/bin/init-wrapper | 18 + 3.14-minimal/s2i/bin/run | 157 +++++++ 3.14-minimal/s2i/bin/usage | 18 + 3.14-minimal/test/__init__.py | 1 + 3.14-minimal/test/app-home-test-app | 1 + 3.14-minimal/test/app-module-test-app | 1 + 3.14-minimal/test/check_imagestreams.py | 1 + 3.14-minimal/test/conftest.py | 1 + .../test/django-different-port-test-app | 1 + 3.14-minimal/test/django-test-app | 1 + .../from-dockerfile/mod_wsgi.Dockerfile.tpl | 1 + .../test/from-dockerfile/uwsgi.Dockerfile.tpl | 20 + .../gunicorn-config-different-port-test-app | 1 + .../test/gunicorn-different-port-test-app | 1 + ...-python-configfile-different-port-test-app | 1 + 3.14-minimal/test/imagestreams | 1 + 3.14-minimal/test/locale-test-app | 1 + .../test/micropipenv-requirements-test-app | 1 + .../test/micropipenv-test-app/.gitignore | 57 +++ .../micropipenv-test-app/.s2i/environment | 5 + .../test/micropipenv-test-app/Pipfile | 15 + .../test/micropipenv-test-app/Pipfile.lock | 242 +++++++++++ .../test/micropipenv-test-app/setup.py | 10 + .../test/micropipenv-test-app/testapp.py | 12 + 3.14-minimal/test/numpy-test-app | 1 + ...ipenv-and-micropipenv-should-fail-test-app | 1 + 3.14-minimal/test/pipenv-test-app/.gitignore | 57 +++ .../test/pipenv-test-app/.s2i/environment | 2 + 3.14-minimal/test/pipenv-test-app/Pipfile | 15 + .../test/pipenv-test-app/Pipfile.lock | 242 +++++++++++ 3.14-minimal/test/pipenv-test-app/setup.py | 10 + 3.14-minimal/test/pipenv-test-app/testapp.py | 12 + 3.14-minimal/test/poetry-src-layout-test-app | 1 + .../pyuwsgi-pipenv-test-app/.s2i/environment | 2 + .../test/pyuwsgi-pipenv-test-app/app.sh | 23 ++ .../pyuwsgi-pipenv-test-app/requirements.txt | 2 + .../test/pyuwsgi-pipenv-test-app/wsgi.py | 9 + 3.14-minimal/test/run | 308 ++++++++++++++ 3.14-minimal/test/run-openshift-pytest | 1 + .../test/run-openshift-remote-cluster | 1 + 3.14-minimal/test/run-pytest | 15 + 3.14-minimal/test/setup-cfg-test-app | 1 + 3.14-minimal/test/setup-requirements-test-app | 1 + 3.14-minimal/test/setup-test-app | 1 + .../standalone-custom-pypi-index-test-app | 1 + 3.14-minimal/test/standalone-test-app | 1 + 3.14-minimal/test/test-lib-openshift.sh | 1 + 3.14-minimal/test/test-lib-python.sh | 1 + .../test/test-lib-remote-openshift.sh | 1 + 3.14-minimal/test/test-lib.sh | 1 + 3.14-minimal/test/test-openshift.yaml | 1 + .../test/test_container_application.py | 1 + 3.14-minimal/test/test_container_basics.py | 1 + .../test/test_ocp_deploy_templates.py | 1 + .../test/test_ocp_helm_python_django_app.py | 1 + ..._ocp_helm_python_django_psql_persistent.py | 1 + .../test/test_ocp_helm_python_imagestreams.py | 1 + .../test/test_ocp_imagestreams_quickstart.py | 1 + .../test/test_ocp_python_ex_standalone.py | 1 + .../test/test_ocp_python_ex_template.py | 1 + 3.14-minimal/test/uwsgi-test-app | 1 + 3.14/Dockerfile.fedora | 88 ++++ 3.14/README.md | 386 ++++++++++++++++++ .../opt/app-root/etc/generate_container_user | 19 + 3.14/root/opt/wheels | 1 + 3.14/s2i/bin/assemble | 134 ++++++ 3.14/s2i/bin/init-wrapper | 18 + 3.14/s2i/bin/run | 155 +++++++ 3.14/s2i/bin/usage | 18 + 3.14/test/__init__.py | 1 + 3.14/test/app-home-test-app | 1 + 3.14/test/app-module-test-app | 1 + 3.14/test/check_imagestreams.py | 1 + 3.14/test/conftest.py | 1 + 3.14/test/django-different-port-test-app | 1 + 3.14/test/django-test-app | 1 + 3.14/test/from-dockerfile/Dockerfile.tpl | 1 + .../from-dockerfile/Dockerfile_no_s2i.tpl | 16 + .../gunicorn-config-different-port-test-app | 1 + 3.14/test/gunicorn-different-port-test-app | 1 + ...-python-configfile-different-port-test-app | 1 + 3.14/test/imagestreams | 1 + 3.14/test/locale-test-app | 1 + 3.14/test/micropipenv-requirements-test-app | 1 + 3.14/test/micropipenv-test-app/.gitignore | 57 +++ .../micropipenv-test-app/.s2i/environment | 5 + 3.14/test/micropipenv-test-app/Pipfile | 15 + 3.14/test/micropipenv-test-app/Pipfile.lock | 242 +++++++++++ 3.14/test/micropipenv-test-app/setup.py | 10 + 3.14/test/micropipenv-test-app/testapp.py | 12 + 3.14/test/mod-wsgi-test-app | 1 + 3.14/test/npm-virtualenv-uwsgi-test-app | 1 + 3.14/test/numpy-test-app | 1 + .../.s2i/environment | 2 + 3.14/test/pin-pipenv-version-test-app/app.sh | 24 ++ .../requirements.txt | 2 + 3.14/test/pin-pipenv-version-test-app/wsgi.py | 9 + ...ipenv-and-micropipenv-should-fail-test-app | 1 + 3.14/test/pipenv-test-app/.gitignore | 57 +++ 3.14/test/pipenv-test-app/.s2i/environment | 2 + 3.14/test/pipenv-test-app/Pipfile | 15 + 3.14/test/pipenv-test-app/Pipfile.lock | 242 +++++++++++ 3.14/test/pipenv-test-app/setup.py | 10 + 3.14/test/pipenv-test-app/testapp.py | 12 + 3.14/test/poetry-src-layout-test-app | 1 + .../pyuwsgi-pipenv-test-app/.s2i/environment | 2 + 3.14/test/pyuwsgi-pipenv-test-app/app.sh | 23 ++ .../pyuwsgi-pipenv-test-app/requirements.txt | 2 + 3.14/test/pyuwsgi-pipenv-test-app/wsgi.py | 9 + 3.14/test/run | 312 ++++++++++++++ 3.14/test/run-openshift-pytest | 1 + 3.14/test/run-openshift-remote-cluster | 1 + 3.14/test/run-pytest | 15 + 3.14/test/setup-cfg-test-app | 1 + 3.14/test/setup-requirements-test-app | 1 + 3.14/test/setup-test-app | 1 + .../standalone-custom-pypi-index-test-app | 1 + 3.14/test/standalone-test-app | 1 + 3.14/test/test-lib-openshift.sh | 1 + 3.14/test/test-lib-python.sh | 1 + 3.14/test/test-lib-remote-openshift.sh | 1 + 3.14/test/test-lib.sh | 1 + 3.14/test/test-openshift.yaml | 1 + 3.14/test/test_container_application.py | 1 + 3.14/test/test_container_basics.py | 1 + 3.14/test/test_ocp_deploy_templates.py | 1 + 3.14/test/test_ocp_helm_python_django_app.py | 1 + ..._ocp_helm_python_django_psql_persistent.py | 1 + .../test/test_ocp_helm_python_imagestreams.py | 1 + 3.14/test/test_ocp_imagestreams_quickstart.py | 1 + 3.14/test/test_ocp_python_ex_standalone.py | 1 + 3.14/test/test_ocp_python_ex_template.py | 1 + 140 files changed, 3865 insertions(+) create mode 100644 3.14-minimal/Dockerfile.fedora create mode 100644 3.14-minimal/README.md create mode 100644 3.14-minimal/root/opt/app-root/etc/generate_container_user create mode 120000 3.14-minimal/root/opt/wheels create mode 100755 3.14-minimal/root/usr/bin/cgroup-limits create mode 100755 3.14-minimal/root/usr/bin/fix-permissions create mode 100755 3.14-minimal/root/usr/bin/rpm-file-permissions create mode 100755 3.14-minimal/s2i/bin/assemble create mode 100755 3.14-minimal/s2i/bin/init-wrapper create mode 100755 3.14-minimal/s2i/bin/run create mode 100755 3.14-minimal/s2i/bin/usage create mode 120000 3.14-minimal/test/__init__.py create mode 120000 3.14-minimal/test/app-home-test-app create mode 120000 3.14-minimal/test/app-module-test-app create mode 120000 3.14-minimal/test/check_imagestreams.py create mode 120000 3.14-minimal/test/conftest.py create mode 120000 3.14-minimal/test/django-different-port-test-app create mode 120000 3.14-minimal/test/django-test-app create mode 120000 3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl create mode 100644 3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl create mode 120000 3.14-minimal/test/gunicorn-config-different-port-test-app create mode 120000 3.14-minimal/test/gunicorn-different-port-test-app create mode 120000 3.14-minimal/test/gunicorn-python-configfile-different-port-test-app create mode 120000 3.14-minimal/test/imagestreams create mode 120000 3.14-minimal/test/locale-test-app create mode 120000 3.14-minimal/test/micropipenv-requirements-test-app create mode 100644 3.14-minimal/test/micropipenv-test-app/.gitignore create mode 100644 3.14-minimal/test/micropipenv-test-app/.s2i/environment create mode 100644 3.14-minimal/test/micropipenv-test-app/Pipfile create mode 100644 3.14-minimal/test/micropipenv-test-app/Pipfile.lock create mode 100644 3.14-minimal/test/micropipenv-test-app/setup.py create mode 100644 3.14-minimal/test/micropipenv-test-app/testapp.py create mode 120000 3.14-minimal/test/numpy-test-app create mode 120000 3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app create mode 100644 3.14-minimal/test/pipenv-test-app/.gitignore create mode 100644 3.14-minimal/test/pipenv-test-app/.s2i/environment create mode 100644 3.14-minimal/test/pipenv-test-app/Pipfile create mode 100644 3.14-minimal/test/pipenv-test-app/Pipfile.lock create mode 100644 3.14-minimal/test/pipenv-test-app/setup.py create mode 100644 3.14-minimal/test/pipenv-test-app/testapp.py create mode 120000 3.14-minimal/test/poetry-src-layout-test-app create mode 100644 3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment create mode 100755 3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh create mode 100644 3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt create mode 100644 3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py create mode 100755 3.14-minimal/test/run create mode 120000 3.14-minimal/test/run-openshift-pytest create mode 120000 3.14-minimal/test/run-openshift-remote-cluster create mode 100755 3.14-minimal/test/run-pytest create mode 120000 3.14-minimal/test/setup-cfg-test-app create mode 120000 3.14-minimal/test/setup-requirements-test-app create mode 120000 3.14-minimal/test/setup-test-app create mode 120000 3.14-minimal/test/standalone-custom-pypi-index-test-app create mode 120000 3.14-minimal/test/standalone-test-app create mode 120000 3.14-minimal/test/test-lib-openshift.sh create mode 120000 3.14-minimal/test/test-lib-python.sh create mode 120000 3.14-minimal/test/test-lib-remote-openshift.sh create mode 120000 3.14-minimal/test/test-lib.sh create mode 120000 3.14-minimal/test/test-openshift.yaml create mode 120000 3.14-minimal/test/test_container_application.py create mode 120000 3.14-minimal/test/test_container_basics.py create mode 120000 3.14-minimal/test/test_ocp_deploy_templates.py create mode 120000 3.14-minimal/test/test_ocp_helm_python_django_app.py create mode 120000 3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py create mode 120000 3.14-minimal/test/test_ocp_helm_python_imagestreams.py create mode 120000 3.14-minimal/test/test_ocp_imagestreams_quickstart.py create mode 120000 3.14-minimal/test/test_ocp_python_ex_standalone.py create mode 120000 3.14-minimal/test/test_ocp_python_ex_template.py create mode 120000 3.14-minimal/test/uwsgi-test-app create mode 100644 3.14/Dockerfile.fedora create mode 100644 3.14/README.md create mode 100644 3.14/root/opt/app-root/etc/generate_container_user create mode 120000 3.14/root/opt/wheels create mode 100755 3.14/s2i/bin/assemble create mode 100755 3.14/s2i/bin/init-wrapper create mode 100755 3.14/s2i/bin/run create mode 100755 3.14/s2i/bin/usage create mode 120000 3.14/test/__init__.py create mode 120000 3.14/test/app-home-test-app create mode 120000 3.14/test/app-module-test-app create mode 120000 3.14/test/check_imagestreams.py create mode 120000 3.14/test/conftest.py create mode 120000 3.14/test/django-different-port-test-app create mode 120000 3.14/test/django-test-app create mode 120000 3.14/test/from-dockerfile/Dockerfile.tpl create mode 100644 3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl create mode 120000 3.14/test/gunicorn-config-different-port-test-app create mode 120000 3.14/test/gunicorn-different-port-test-app create mode 120000 3.14/test/gunicorn-python-configfile-different-port-test-app create mode 120000 3.14/test/imagestreams create mode 120000 3.14/test/locale-test-app create mode 120000 3.14/test/micropipenv-requirements-test-app create mode 100644 3.14/test/micropipenv-test-app/.gitignore create mode 100644 3.14/test/micropipenv-test-app/.s2i/environment create mode 100644 3.14/test/micropipenv-test-app/Pipfile create mode 100644 3.14/test/micropipenv-test-app/Pipfile.lock create mode 100644 3.14/test/micropipenv-test-app/setup.py create mode 100644 3.14/test/micropipenv-test-app/testapp.py create mode 120000 3.14/test/mod-wsgi-test-app create mode 120000 3.14/test/npm-virtualenv-uwsgi-test-app create mode 120000 3.14/test/numpy-test-app create mode 100644 3.14/test/pin-pipenv-version-test-app/.s2i/environment create mode 100755 3.14/test/pin-pipenv-version-test-app/app.sh create mode 100644 3.14/test/pin-pipenv-version-test-app/requirements.txt create mode 100644 3.14/test/pin-pipenv-version-test-app/wsgi.py create mode 120000 3.14/test/pipenv-and-micropipenv-should-fail-test-app create mode 100644 3.14/test/pipenv-test-app/.gitignore create mode 100644 3.14/test/pipenv-test-app/.s2i/environment create mode 100644 3.14/test/pipenv-test-app/Pipfile create mode 100644 3.14/test/pipenv-test-app/Pipfile.lock create mode 100644 3.14/test/pipenv-test-app/setup.py create mode 100644 3.14/test/pipenv-test-app/testapp.py create mode 120000 3.14/test/poetry-src-layout-test-app create mode 100644 3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment create mode 100755 3.14/test/pyuwsgi-pipenv-test-app/app.sh create mode 100644 3.14/test/pyuwsgi-pipenv-test-app/requirements.txt create mode 100644 3.14/test/pyuwsgi-pipenv-test-app/wsgi.py create mode 100755 3.14/test/run create mode 120000 3.14/test/run-openshift-pytest create mode 120000 3.14/test/run-openshift-remote-cluster create mode 100755 3.14/test/run-pytest create mode 120000 3.14/test/setup-cfg-test-app create mode 120000 3.14/test/setup-requirements-test-app create mode 120000 3.14/test/setup-test-app create mode 120000 3.14/test/standalone-custom-pypi-index-test-app create mode 120000 3.14/test/standalone-test-app create mode 120000 3.14/test/test-lib-openshift.sh create mode 120000 3.14/test/test-lib-python.sh create mode 120000 3.14/test/test-lib-remote-openshift.sh create mode 120000 3.14/test/test-lib.sh create mode 120000 3.14/test/test-openshift.yaml create mode 120000 3.14/test/test_container_application.py create mode 120000 3.14/test/test_container_basics.py create mode 120000 3.14/test/test_ocp_deploy_templates.py create mode 120000 3.14/test/test_ocp_helm_python_django_app.py create mode 120000 3.14/test/test_ocp_helm_python_django_psql_persistent.py create mode 120000 3.14/test/test_ocp_helm_python_imagestreams.py create mode 120000 3.14/test/test_ocp_imagestreams_quickstart.py create mode 120000 3.14/test/test_ocp_python_ex_standalone.py create mode 120000 3.14/test/test_ocp_python_ex_template.py diff --git a/3.14-minimal/Dockerfile.fedora b/3.14-minimal/Dockerfile.fedora new file mode 100644 index 00000000..ca0fde42 --- /dev/null +++ b/3.14-minimal/Dockerfile.fedora @@ -0,0 +1,104 @@ +FROM quay.io/fedora/fedora-minimal:43 + + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + CNB_STACK_ID=com.redhat.stacks.ubi-python-314 \ + CNB_USER_ID=1001 \ + CNB_GROUP_ID=0 \ + PIP_NO_CACHE_DIR=off \ + # The following variables are usually available from parent s2i images \ + STI_SCRIPTS_PATH=/usr/libexec/s2i \ + APP_ROOT=/opt/app-root \ + HOME=/opt/app-root/src \ + PLATFORM="el" + +# /opt/app-root/bin - the main venv +# /opt/app-root/src/bin - app-specific binaries +# /opt/app-root/src/.local/bin - tools like pipenv +ENV PATH=$APP_ROOT/bin:$HOME/bin:$HOME/.local/bin:$PATH + +# Ensure the virtual environment is active in interactive shells +ENV BASH_ENV=${APP_ROOT}/bin/activate \ + ENV=${APP_ROOT}/bin/activate \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +ENV SUMMARY="Minimal platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as container is a base platform for \ +building and running various Python $PYTHON_VERSION applications and frameworks. \ +Python is an easy to learn, powerful programming language. It has efficient high-level \ +data structures and a simple but effective approach to object-oriented programming. \ +Python's elegant syntax and dynamic typing, together with its interpreted nature, \ +make it an ideal language for scripting and rapid application development in many areas \ +on most platforms." + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="Python 3.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="python-314-container" \ + name="fedora/python-314-minimal" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14-minimal/test/setup-test-app/ ubi/python-314-minimal python-sample-app" \ + com.redhat.license_terms="https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI" \ + io.buildpacks.stack.id="com.redhat.stacks.ubi-python-314-minimal" \ + maintainer="SoftwareCollections.org " + +# Very minimal set of packages +# Python is obvious in the Python container :) +# glibc-langpack-en is needed to set locale to en_US and disable warning about it +# findutils - find command is needed for fix-permissions script +# nss_wrapper - used in generate_container_user script +RUN INSTALL_PKGS="python3.14 glibc-langpack-en findutils nss_wrapper-libs" && \ + microdnf -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 install $INSTALL_PKGS && \ + microdnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14-minimal/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14-minimal/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14-minimal/root/opt/wheels /opt/wheels + +# This command sets (and also creates if necessary) +# the home directory - it has to be done here so the latter +# fix-permissions fixes this directory as well. +WORKDIR ${HOME} + +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - In order to drop the root user, we have to make some directories world +# writable as OpenShift default security model is to run the container +# under random UID. +RUN \ + python3.14 -m venv ${APP_ROOT} && \ + # We have to upgrade pip to a newer version because \ + # pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ + # support platforms like ppc64le, aarch64 or armv7 \ + # We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ + # because it's tested better then whatever version from PyPI and contains useful patches. \ + # We have to do it here so the permissions are correctly fixed and pip is able \ + # to reinstall itself in the next build phases in the assemble script if user wants the latest version \ + ${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ + rm -r /opt/wheels && \ + chown -R 1001:0 ${APP_ROOT} && \ + fix-permissions ${APP_ROOT} -P && \ + rpm-file-permissions + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14-minimal/README.md b/3.14-minimal/README.md new file mode 100644 index 00000000..4b80e114 --- /dev/null +++ b/3.14-minimal/README.md @@ -0,0 +1,123 @@ +Python 3.14 container image - minimal version +============================================ + +This container image is a special version of the [full Python 3.14 container image](https://github.com/sclorg/s2i-python-container/tree/master/3.14) +provided as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.14 applications. + +Because the minimal and full images work similarly, we document here only the differences and limitations +of the minimal container image. For the documentation of common features see the [full container image docs](https://github.com/sclorg/s2i-python-container/tree/master/3.14). + +The Python 3.14 minimal container image is currently considered a tech-preview and only available on quay.io. +The image is built on top of the [official CentOS Stream base containers](quay.io/centos/centos). + +To pull the Python 3.14 minimal container image to build on, run + +``` +podman pull quay.io/fedora/python-314-minimal +``` + +Description +----------- + +The full container image is a universal base image to build your containerized applications on top of. However, its universal nature +means that the resulting containers it produces consume a lot of disk space. This is caused mainly by the fact that the image contains +npm, compilers, header files and some other packages one might need to install and deploy their applications. + +Because size does matter for us and our customers, we have prepared this minimal container image with very limited subset +of installed packages. There are no compilers, no header files, no npm etc and the yum package manager is replaced with a minimalistic +reimplementation called microdnf, making the resulting container images much smaller. This creates some limitations +but we provide ways to workaround them. + +Limitations +----------- + +1. There is only a very limited subset of packages installed. They are choosen carefully to satisfy most of the Python apps but your app might have some special needs. +1. There is no npm and nodejs. +1. There are no compilers and header files. Installation from Python wheels should still work but compilation from a source code is not supported out of the box. + +In the next chapter, we provide three possible workarounds for the mentioned limitations of the minimal container image. + +Possible solutions for the limitations +-------------------------------------- + +### Use the full container image + +It's easy at that. If you don't want to write your own Dockerfile and disk space is not a problem, use +the full universal container image and you should be fine. + +### Build your own container image on top of the minimal container image + +Let's say that your application depends on uwsgi. uwsgi cannot be installed from Python wheel and has to be +compiled from source which requires some additional packages to be installed - namely gcc for the compilation +itself and python3.14-devel containing Python header files. + +To solve that problem, you can use all the pieces provided by the minimal container image and just add one more +step to install the missing dependencies: + +``` +FROM python-314-minimal + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src + +# Install packages necessary for compiling uwsgi from source +RUN microdnf install -y gcc python3.14-devel +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you do it this way, your problem with the missing packages is solved. But there is also one disadvantage: the resulting +runtime image contains unnecessary compiler and Python header files. How to solve this? Uninstalling them at the end +of the Dockerfile is not really a solution but we have one. Keep reading. + +### Build on full image, run on minimal image + +Did you know that you can copy files from one image to another one during a build? That's the feature we are gonna use now. +We use the full container image with all compilers and other usefull packages installed to build our app and its dependencies +and we then move the result including the whole virtual environemnt to the minimal container image. + +This app needs mod_wsgi and to install (compile it from source) it, we'll need: httpd-devel for header files, gcc and redhat-rpm-config +as a compiler and configuratuion and finally python3.14-devel containing Python header files. There is no need to install those packages +manually because the full container image already contains them. However, the application needs httpd as a runtime dependency +so we need to install it to the minimal container image as well. + +``` +# Part 1 - build + +FROM python-314 as builder + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the application's dependencies from PyPI +RUN /usr/libexec/s2i/assemble + +# Part 2 - deploy + +FROM python-314-minimal + +# Copy app sources together with the whole virtual environment from the builder image +COPY --from=builder $APP_ROOT $APP_ROOT + +# Install httpd package - runtime dependency of our application +USER 0 +RUN microdnf install -y httpd +USER 1001 + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +This way, the resulting container image does contain only necessary dependencies and it's much lighter. diff --git a/3.14-minimal/root/opt/app-root/etc/generate_container_user b/3.14-minimal/root/opt/app-root/etc/generate_container_user new file mode 100644 index 00000000..a7fd74d6 --- /dev/null +++ b/3.14-minimal/root/opt/app-root/etc/generate_container_user @@ -0,0 +1,19 @@ +# Set current user in nss_wrapper +USER_ID=$(id -u) +GROUP_ID=$(id -g) + +if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then + + NSS_WRAPPER_PASSWD=/opt/app-root/etc/passwd + NSS_WRAPPER_GROUP=/etc/group + + cat /etc/passwd | sed -e 's/^default:/builder:/' > $NSS_WRAPPER_PASSWD + + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/3.14-minimal/root/opt/wheels b/3.14-minimal/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.14-minimal/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.14-minimal/root/usr/bin/cgroup-limits b/3.14-minimal/root/usr/bin/cgroup-limits new file mode 100755 index 00000000..6fa19153 --- /dev/null +++ b/3.14-minimal/root/usr/bin/cgroup-limits @@ -0,0 +1,192 @@ +#!/usr/libexec/platform-python + +""" +Script for parsing cgroup information + +This script will read some limits from the cgroup system and parse +them, printing out "VARIABLE=VALUE" on each line for every limit that is +successfully read. Output of this script can be directly fed into +bash's export command. Recommended usage from a bash script: + + set -o errexit + export_vars=$(cgroup-limits) ; export $export_vars + +Variables currently supported: + MAX_MEMORY_LIMIT_IN_BYTES + Maximum possible limit MEMORY_LIMIT_IN_BYTES can have. This is + currently constant value of 9223372036854775807. + MEMORY_LIMIT_IN_BYTES + Maximum amount of user memory in bytes. If this value is set + to the same value as MAX_MEMORY_LIMIT_IN_BYTES, it means that + there is no limit set. The value is taken from + /sys/fs/cgroup/memory/memory.limit_in_bytes for cgroups v1 + and from /sys/fs/cgroup/memory.max for cgroups v2 + NUMBER_OF_CORES + Number of CPU cores that can be used. If both the cpu and cpuset + controllers specify a limit, the controller with the lowest CPU + limit takes precedence. For the cpu controller, the value is + calculated from /sys/fs/cgroup/cpu/cpu.cfs_{quota,period}_us for + cgroups v1 and from /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2. + For the cpuset controller, the value is taken from + /sys/fs/cgroup/cpuset/cpuset.cpus for cgroups v1 and from + /sys/fs/cgroup/cpuset.cpus.effective for cgroups v2 + NO_MEMORY_LIMIT + Set to "true" if MEMORY_LIMIT_IN_BYTES is so high that the caller + can act as if no memory limit was set. Undefined otherwise. + +Note about non-root containers: + + Per podman-run(1) man page, on some systems, changing the resource limits + may not be allowed for non-root users. For more details, see + https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-resource-limits-fails-with-a-permissions-error. + +How to test this script: + + Run a container as root and see whether the output of available memory + and CPUs match what is either available on the host or specified via + cgroups limits by the container runtime (podman). + + For example: + + # This should return NO_MEMORY_LIMIT=true, NUMBER_OF_CORES= + sudo podman run -ti --rm quay.io/fedora/s2i-core /usr/bin/cgroup-limits + + # This should return MEMORY_LIMIT_IN_BYTES=2147483648, NUMBER_OF_CORES=3 + # 3 CPUs despite --cpus 4 was given is correct, because the cpuset 2-4 means + # running on 3 concrete processors only, which is lower limit than --cpus + # and thus is preferred + sudo podman run -ti --rm --cpus 4 --cpuset-cpus=2-4 --memory 2G quay.io/fedora/s2i-core /usr/bin/cgroup-limits +""" + +from __future__ import division +from __future__ import print_function +import sys +import subprocess + + +def _read_file(path): + try: + with open(path, 'r') as f: + return f.read().strip() + except IOError: + return None + + +def get_memory_limit(): + """ + Read memory limit, in bytes. + """ + + limit = _read_file('/sys/fs/cgroup/memory/memory.limit_in_bytes') + # If first file does not exist, try cgroups v2 file + limit = limit or _read_file('/sys/fs/cgroup/memory.max') + if limit is None or not limit.isdigit(): + if limit == 'max': + return 9223372036854775807 + print("Warning: Can't detect memory limit from cgroups", + file=sys.stderr) + return None + return int(limit) + + +def get_number_of_cores(): + """ + Read number of CPU cores. + """ + + limits = [l for l in [_read_cpu_quota(), _read_cpuset_size(), _read_nproc()] if l] + if not limits: + return None + return min(limits) + + +def _read_cpu_quota(): + + quota, period = None, None + + quota = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + if quota: + # cgroups v1 + quota = quota.strip() + if quota == "-1": + return None + + period = _read_file("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + if period: + period = period.strip() + + else: + # cgroups v2 + line = _read_file("/sys/fs/cgroup/cpu.max") + if line: + fields = line.split() + + if len(fields) >= 2: + quota = fields[0] + if quota == "max": + return None + period = fields[1] + + + if quota and quota.isdigit() and period and period.isdigit(): + return int(quota)//int(period) + + print("Warning: Can't detect cpu quota from cgroups", + file=sys.stderr) + return None + + +def _read_cpuset_size(): + + core_count = 0 + + line = _read_file('/sys/fs/cgroup/cpuset/cpuset.cpus') + # If first file does not exist, try cgroups v2 file + line = line or _read_file('/sys/fs/cgroup/cpuset.cpus.effective') + if line is None: + # None of the files above exists when running podman as non-root, + # so in that case, this warning is printed every-time + print("Warning: Can't detect cpuset size from cgroups, will use nproc", + file=sys.stderr) + return None + + for group in line.split(','): + core_ids = list(map(int, group.split('-'))) + if len(core_ids) == 2: + core_count += core_ids[1] - core_ids[0] + 1 + else: + core_count += 1 + + return core_count + + +def _read_nproc(): + """ + Returns number of cores without looking at cgroup limits. + This might work as the last resort when running a container as non-root + where cgroups v2 resource limits cannot be set without a specific + configuration (per podman-run(1) man page). + """ + try: + stdout, stderr = subprocess.Popen('nproc', stdout=subprocess.PIPE).communicate() + return int(stdout) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return None + + +if __name__ == "__main__": + env_vars = { + "MAX_MEMORY_LIMIT_IN_BYTES": 9223372036854775807, + "MEMORY_LIMIT_IN_BYTES": get_memory_limit(), + "NUMBER_OF_CORES": get_number_of_cores() + } + + env_vars = {k: v for k, v in env_vars.items() if v is not None} + + if env_vars.get("MEMORY_LIMIT_IN_BYTES", 0) >= 92233720368547: + env_vars["NO_MEMORY_LIMIT"] = "true" + + for key, value in env_vars.items(): + print("{0}={1}".format(key, value)) diff --git a/3.14-minimal/root/usr/bin/fix-permissions b/3.14-minimal/root/usr/bin/fix-permissions new file mode 100755 index 00000000..ddd33ace --- /dev/null +++ b/3.14-minimal/root/usr/bin/fix-permissions @@ -0,0 +1,27 @@ +#!/bin/sh + +# Allow this script to fail without failing a build +set +e + +SYMLINK_OPT=${2:--L} + +# Fix permissions on the given directory or file to allow group read/write of +# regular files and execute of directories. + +[ $(id -u) -ne 0 ] && CHECK_OWNER=" -uid $(id -u)" + +# If argument does not exist, script will still exit with 0, +# but at least we'll see something went wrong in the log +if ! [ -e "$1" ] ; then + echo "ERROR: File or directory $1 does not exist." >&2 + # We still want to end successfully + exit 0 +fi + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -gid 0 -exec chgrp 0 {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} \! -perm -g+rw -exec chmod g+rw {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -perm /u+x -a \! -perm /g+x -exec chmod g+x {} + +find $SYMLINK_OPT "$1" ${CHECK_OWNER} -type d \! -perm /g+x -exec chmod g+x {} + + +# Always end successfully +exit 0 diff --git a/3.14-minimal/root/usr/bin/rpm-file-permissions b/3.14-minimal/root/usr/bin/rpm-file-permissions new file mode 100755 index 00000000..8be1fb0d --- /dev/null +++ b/3.14-minimal/root/usr/bin/rpm-file-permissions @@ -0,0 +1,21 @@ +#!/bin/sh + +CHECK_DIRS="/ /opt /etc /usr /usr/bin /usr/lib /usr/lib64 /usr/share /usr/libexec" + +rpm_format="[%{FILESTATES:fstate} %7{FILEMODES:octal} %{FILENAMES:shescape}\n]" + +rpm -q --qf "$rpm_format" filesystem | while read line +do + eval "set -- $line" + + case $1 in + normal) ;; + *) continue ;; + esac + + case " $CHECK_DIRS " in + *" $3 "*) + chmod "${2: -4}" "$3" + ;; + esac +done diff --git a/3.14-minimal/s2i/bin/assemble b/3.14-minimal/s2i/bin/assemble new file mode 100755 index 00000000..b5cfeb6f --- /dev/null +++ b/3.14-minimal/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.14 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./manage.py + + if [[ ! -f "$manage_file" ]]; then + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "'manage.py collectstatic' ignored." + exit + fi + + if ! python $manage_file collectstatic --dry-run --noinput &> /dev/null; then + echo "WARNING: could not run 'manage.py collectstatic'. To debug, run:" + echo " $ python $manage_file collectstatic --noinput" + echo "Ignore this warning if you're not serving static files with Django." + exit + fi + + python $manage_file collectstatic --noinput + ) +fi + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P diff --git a/3.14-minimal/s2i/bin/init-wrapper b/3.14-minimal/s2i/bin/init-wrapper new file mode 100755 index 00000000..26c8767d --- /dev/null +++ b/3.14-minimal/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS diff --git a/3.14-minimal/s2i/bin/run b/3.14-minimal/s2i/bin/run new file mode 100755 index 00000000..bc0b3a15 --- /dev/null +++ b/3.14-minimal/s2i/bin/run @@ -0,0 +1,157 @@ +#!/bin/bash +source /opt/app-root/etc/generate_container_user + +set -e + +function is_gunicorn_installed() { + hash gunicorn &>/dev/null +} + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_migrate() { + is_django_installed && [[ -z "$DISABLE_MIGRATE" ]] +} + +# Guess the number of workers according to the number of cores +function get_default_web_concurrency() { + # Using python and just python here because the script has + # platform-python in its shebang which we don't have in the minimal image + limit_vars=$(python /usr/bin/cgroup-limits) + local $limit_vars + if [ -z "${NUMBER_OF_CORES:-}" ]; then + echo 1 + return + fi + + local max=$((NUMBER_OF_CORES*2)) + # Require at least 43 MiB and additional 40 MiB for every worker + local default=$(((${MEMORY_LIMIT_IN_BYTES:-MAX_MEMORY_LIMIT_IN_BYTES}/1024/1024 - 43) / 40)) + default=$((default > max ? max : default)) + default=$((default < 1 ? 1 : default)) + # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, + # 12 workers should be enough to handle hundreds or thousands requests per second + default=$((default > 12 ? 12 : default)) + echo $default +} + +function maybe_run_in_init_wrapper() { + if [[ -z "$ENABLE_INIT_WRAPPER" ]]; then + exec "$@" + else + exec $STI_SCRIPTS_PATH/init-wrapper "$@" + fi +} + +# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py +if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + +APP_HOME=$(readlink -f "${APP_HOME:-.}") +# Change the working directory to APP_HOME +PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" +cd "$APP_HOME" + +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 + fi +fi + +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi +fi + +# Look for 'manage.py' in the current directory +manage_file=./manage.py + +if should_migrate; then + if [[ -f "$manage_file" ]]; then + echo "---> Migrating database ..." + python "$manage_file" migrate --noinput + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py migrate'." + fi +fi + +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + +if is_gunicorn_installed; then + setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) + # Look for wsgi module in the current directory + if [[ -z "$APP_MODULE" && -f "./wsgi.py" ]]; then + APP_MODULE=wsgi + elif [[ -z "$APP_MODULE" && -f "$setup_py" ]]; then + APP_MODULE="$(python "$setup_py" --name)" + fi + + if [[ "$APP_MODULE" ]]; then + export WEB_CONCURRENCY=${WEB_CONCURRENCY:-$(get_default_web_concurrency)} + + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py runserver'." + fi +fi + +>&2 echo "ERROR: don't know how to run your application." +>&2 echo "Please set either APP_MODULE, APP_FILE or APP_SCRIPT environment variables, or create a file 'app.py' to launch your application." +exit 1 diff --git a/3.14-minimal/s2i/bin/usage b/3.14-minimal/s2i/bin/usage new file mode 100755 index 00000000..8b449de5 --- /dev/null +++ b/3.14-minimal/s2i/bin/usage @@ -0,0 +1,18 @@ +#!/bin/sh + +DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` +NAMESPACE=centos +[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl + +cat < -- curl 127.0.0.1:8080 +EOF diff --git a/3.14-minimal/test/__init__.py b/3.14-minimal/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.14-minimal/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.14-minimal/test/app-home-test-app b/3.14-minimal/test/app-home-test-app new file mode 120000 index 00000000..f6b42e77 --- /dev/null +++ b/3.14-minimal/test/app-home-test-app @@ -0,0 +1 @@ +../../examples/app-home-test-app \ No newline at end of file diff --git a/3.14-minimal/test/app-module-test-app b/3.14-minimal/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.14-minimal/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.14-minimal/test/check_imagestreams.py b/3.14-minimal/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.14-minimal/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.14-minimal/test/conftest.py b/3.14-minimal/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.14-minimal/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.14-minimal/test/django-different-port-test-app b/3.14-minimal/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.14-minimal/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/django-test-app b/3.14-minimal/test/django-test-app new file mode 120000 index 00000000..52cebf3b --- /dev/null +++ b/3.14-minimal/test/django-test-app @@ -0,0 +1 @@ +../../examples/django-test-app \ No newline at end of file diff --git a/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl b/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl new file mode 120000 index 00000000..f5c5b907 --- /dev/null +++ b/3.14-minimal/test/from-dockerfile/mod_wsgi.Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/mod_wsgi.Dockerfile.tpl \ No newline at end of file diff --git a/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl b/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl new file mode 100644 index 00000000..874927da --- /dev/null +++ b/3.14-minimal/test/from-dockerfile/uwsgi.Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +ENV UPGRADE_PIP_TO_LATEST=1 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +# Install packages necessary for compiling uwsgi from source +# pkgconfig(python-3.14) is provided by both python3-devel in c9s +# and python3.14-devel in UBI8. +RUN microdnf install -y gcc "pkgconfig(python-3.14)" which +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run diff --git a/3.14-minimal/test/gunicorn-config-different-port-test-app b/3.14-minimal/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.14-minimal/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/gunicorn-different-port-test-app b/3.14-minimal/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.14-minimal/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app b/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.14-minimal/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.14-minimal/test/imagestreams b/3.14-minimal/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.14-minimal/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.14-minimal/test/locale-test-app b/3.14-minimal/test/locale-test-app new file mode 120000 index 00000000..9105ec08 --- /dev/null +++ b/3.14-minimal/test/locale-test-app @@ -0,0 +1 @@ +../../examples/locale-test-app \ No newline at end of file diff --git a/3.14-minimal/test/micropipenv-requirements-test-app b/3.14-minimal/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.14-minimal/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.14-minimal/test/micropipenv-test-app/.gitignore b/3.14-minimal/test/micropipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/3.14-minimal/test/micropipenv-test-app/.s2i/environment b/3.14-minimal/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/3.14-minimal/test/micropipenv-test-app/Pipfile b/3.14-minimal/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14-minimal/test/micropipenv-test-app/Pipfile.lock b/3.14-minimal/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "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.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14-minimal/test/micropipenv-test-app/setup.py b/3.14-minimal/test/micropipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + + +setup ( + name = "testapp", + version = "0.1", + description = "Example application to be deployed.", + packages = find_packages(), + install_requires = ["gunicorn"], +) diff --git a/3.14-minimal/test/micropipenv-test-app/testapp.py b/3.14-minimal/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14-minimal/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14-minimal/test/numpy-test-app b/3.14-minimal/test/numpy-test-app new file mode 120000 index 00000000..b064f87f --- /dev/null +++ b/3.14-minimal/test/numpy-test-app @@ -0,0 +1 @@ +../../examples/numpy-test-app \ No newline at end of file diff --git a/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app b/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.14-minimal/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.14-minimal/test/pipenv-test-app/.gitignore b/3.14-minimal/test/pipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/3.14-minimal/test/pipenv-test-app/.s2i/environment b/3.14-minimal/test/pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..4a4f7605 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=true +DISABLE_SETUP_PY_PROCESSING=true diff --git a/3.14-minimal/test/pipenv-test-app/Pipfile b/3.14-minimal/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14-minimal/test/pipenv-test-app/Pipfile.lock b/3.14-minimal/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "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.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14-minimal/test/pipenv-test-app/setup.py b/3.14-minimal/test/pipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + + +setup ( + name = "testapp", + version = "0.1", + description = "Example application to be deployed.", + packages = find_packages(), + install_requires = ["gunicorn"], +) diff --git a/3.14-minimal/test/pipenv-test-app/testapp.py b/3.14-minimal/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14-minimal/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14-minimal/test/poetry-src-layout-test-app b/3.14-minimal/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.14-minimal/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh b/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# Test the uwsgi server +exec uwsgi \ + --http-socket :8080 \ + --die-on-term \ + --master \ + --single-interpreter \ + --enable-threads \ + --threads=5 \ + --thunder-lock \ + --module wsgi diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14-minimal/test/pyuwsgi-pipenv-test-app/wsgi.py @@ -0,0 +1,9 @@ +from flask import Flask +application = Flask(__name__) + +@application.route('/') +def hello(): + return b'Hello World from uWSGI hosted WSGI application!' + +if __name__ == '__main__': + application.run() diff --git a/3.14-minimal/test/run b/3.14-minimal/test/run new file mode 100755 index 00000000..8de24fe3 --- /dev/null +++ b/3.14-minimal/test/run @@ -0,0 +1,308 @@ +#!/bin/bash +# +# The 'run' performs a simple test that verifies that S2I image. +# The main focus here is to excersise the S2I scripts. +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# +declare -a COMMON_WEB_APPS=({gunicorn-config-different-port,gunicorn-different-port,django-different-port,standalone,setup,setup-requirements,django,numpy,app-home,locale,pipenv,pipenv-and-micropipenv-should-fail,app-module,pyuwsgi-pipenv,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${MINIMAL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile_minimal +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +info() { + echo -e "\n\e[1m[INFO] $@\e[0m\n" +} + +image_exists() { + docker inspect $1 &>/dev/null +} + +container_exists() { + image_exists $(cat $cid_file) +} + + +container_ip() { + docker inspect --format="{{ .NetworkSettings.IPAddress }}" $(cat $cid_file) +} + +run_s2i_build() { + info "Building the ${1} application image ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 1 + fi + # TODO: S2I build require the application is a valid 'GIT' repository, we + # should remove this restriction in the future when a file:// is used. + info "Preparing to test ${1} ..." + pushd ${test_dir}/${1} >/dev/null + git init + git config user.email "build@localhost" && git config user.name "builder" + git add -A && git commit -m "Sample commit" + popd >/dev/null +} + +run_test_application() { + docker run --user=100001 ${CONTAINER_ARGS} --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp +} + +cleanup() { + info "Cleaning up the test application image" + if image_exists ${IMAGE_NAME}-testapp; then + docker rmi -f ${IMAGE_NAME}-testapp + fi + rm -rf ${test_dir}/${1}/.git +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[/bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/sh -ic "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/sh -ic "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi +} + +test_connection() { + info "Testing the HTTP connection (http://$(container_ip):${test_port}) ${CONTAINER_ARGS} ..." + local max_attempts=30 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + response_code=$(curl -s -w %{http_code} -o /dev/null http://$(container_ip):${test_port}/) + status=$? + if [ $status -eq 0 ]; then + if [ $response_code -eq 200 ]; then + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +test_application() { + local cid_file="$CID_FILE_DIR"/"$(mktemp -u -p . --suffix .cid)" + # Verify that the HTTP connection can be established to test application container + run_test_application & + + # Wait for the container to write it's CID file + wait_for_cid + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.14-minimal/test/run-openshift-pytest b/3.14-minimal/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.14-minimal/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.14-minimal/test/run-openshift-remote-cluster b/3.14-minimal/test/run-openshift-remote-cluster new file mode 120000 index 00000000..1bffcba8 --- /dev/null +++ b/3.14-minimal/test/run-openshift-remote-cluster @@ -0,0 +1 @@ +../../test/run-openshift-remote-cluster \ No newline at end of file diff --git a/3.14-minimal/test/run-pytest b/3.14-minimal/test/run-pytest new file mode 100755 index 00000000..e69959ad --- /dev/null +++ b/3.14-minimal/test/run-pytest @@ -0,0 +1,15 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +PYTHON_VERSION="3.12" +if [[ ! -f "/usr/bin/python$PYTHON_VERSION" ]]; then + PYTHON_VERSION="3.13" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.14-minimal/test/setup-cfg-test-app b/3.14-minimal/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.14-minimal/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.14-minimal/test/setup-requirements-test-app b/3.14-minimal/test/setup-requirements-test-app new file mode 120000 index 00000000..c909ccb9 --- /dev/null +++ b/3.14-minimal/test/setup-requirements-test-app @@ -0,0 +1 @@ +../../examples/setup-requirements-test-app \ No newline at end of file diff --git a/3.14-minimal/test/setup-test-app b/3.14-minimal/test/setup-test-app new file mode 120000 index 00000000..1b49f26f --- /dev/null +++ b/3.14-minimal/test/setup-test-app @@ -0,0 +1 @@ +../../examples/setup-test-app \ No newline at end of file diff --git a/3.14-minimal/test/standalone-custom-pypi-index-test-app b/3.14-minimal/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.14-minimal/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.14-minimal/test/standalone-test-app b/3.14-minimal/test/standalone-test-app new file mode 120000 index 00000000..3f158948 --- /dev/null +++ b/3.14-minimal/test/standalone-test-app @@ -0,0 +1 @@ +../../examples/standalone-test-app \ No newline at end of file diff --git a/3.14-minimal/test/test-lib-openshift.sh b/3.14-minimal/test/test-lib-openshift.sh new file mode 120000 index 00000000..4f9f2996 --- /dev/null +++ b/3.14-minimal/test/test-lib-openshift.sh @@ -0,0 +1 @@ +../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.14-minimal/test/test-lib-python.sh b/3.14-minimal/test/test-lib-python.sh new file mode 120000 index 00000000..0fa72132 --- /dev/null +++ b/3.14-minimal/test/test-lib-python.sh @@ -0,0 +1 @@ +../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.14-minimal/test/test-lib-remote-openshift.sh b/3.14-minimal/test/test-lib-remote-openshift.sh new file mode 120000 index 00000000..92ad2f4d --- /dev/null +++ b/3.14-minimal/test/test-lib-remote-openshift.sh @@ -0,0 +1 @@ +../../common/test-lib-remote-openshift.sh \ No newline at end of file diff --git a/3.14-minimal/test/test-lib.sh b/3.14-minimal/test/test-lib.sh new file mode 120000 index 00000000..1ac99b93 --- /dev/null +++ b/3.14-minimal/test/test-lib.sh @@ -0,0 +1 @@ +../../common/test-lib.sh \ No newline at end of file diff --git a/3.14-minimal/test/test-openshift.yaml b/3.14-minimal/test/test-openshift.yaml new file mode 120000 index 00000000..8613fbba --- /dev/null +++ b/3.14-minimal/test/test-openshift.yaml @@ -0,0 +1 @@ +../../common/test-openshift.yaml \ No newline at end of file diff --git a/3.14-minimal/test/test_container_application.py b/3.14-minimal/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.14-minimal/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.14-minimal/test/test_container_basics.py b/3.14-minimal/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.14-minimal/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_deploy_templates.py b/3.14-minimal/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.14-minimal/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_django_app.py b/3.14-minimal/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py b/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_helm_python_imagestreams.py b/3.14-minimal/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.14-minimal/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_imagestreams_quickstart.py b/3.14-minimal/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.14-minimal/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_python_ex_standalone.py b/3.14-minimal/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.14-minimal/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.14-minimal/test/test_ocp_python_ex_template.py b/3.14-minimal/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.14-minimal/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file diff --git a/3.14-minimal/test/uwsgi-test-app b/3.14-minimal/test/uwsgi-test-app new file mode 120000 index 00000000..c3ccf241 --- /dev/null +++ b/3.14-minimal/test/uwsgi-test-app @@ -0,0 +1 @@ +../../examples/uwsgi-test-app \ No newline at end of file diff --git a/3.14/Dockerfile.fedora b/3.14/Dockerfile.fedora new file mode 100644 index 00000000..b46f3b3f --- /dev/null +++ b/3.14/Dockerfile.fedora @@ -0,0 +1,88 @@ +# This image provides a Python 3.14 environment you can use to run your Python +# applications. +FROM quay.io/fedora/s2i-base:43 + +EXPOSE 8080 + +ENV PYTHON_VERSION=3.14 \ + PATH=$HOME/.local/bin/:$PATH \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + PIP_NO_CACHE_DIR=off + +ENV NAME=python3 \ + ARCH=x86_64 + +ENV SUMMARY="Platform for building and running Python $PYTHON_VERSION applications" \ + DESCRIPTION="Python $PYTHON_VERSION available as container is a base platform for \ +building and running various Python $PYTHON_VERSION applications and frameworks. \ +Python is an easy to learn, powerful programming language. It has efficient high-level \ +data structures and a simple but effective approach to object-oriented programming. \ +Python's elegant syntax and dynamic typing, together with its interpreted nature, \ +make it an ideal language for scripting and rapid application development in many areas \ +on most platforms." + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="Python 3.14" \ + io.openshift.expose-services="8080:http" \ + io.openshift.tags="builder,python,python314,python-314,rh-python314" \ + com.redhat.component="$NAME" \ + name="fedora/$NAME-314" \ + usage="s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.14/test/setup-test-app/ quay.io/fedora/$NAME-314 python-sample-app" \ + maintainer="SoftwareCollections.org " + +RUN INSTALL_PKGS="python3 python3-devel python3-setuptools python3-pip nss_wrapper-libs \ + httpd httpd-devel atlas-devel gcc-gfortran libffi-devel \ + libtool-ltdl enchant redhat-rpm-config krb5-devel" && \ + dnf -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + dnf -y clean all --enablerepo='*' + +# Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. +COPY 3.14/s2i/bin/ $STI_SCRIPTS_PATH + +# Copy extra files to the image. +COPY 3.14/root/ / + +# Python 3.7+ only +# Yes, the directory below is already copied by the previous command. +# The problem here is that the wheels directory is copied as a symlink. +# Only if you specify symlink directly as a source, COPY copies all the +# files from the symlink destination. +COPY 3.14/root/opt/wheels /opt/wheels +# - Create a Python virtual environment for use by any application to avoid +# potential conflicts with Python packages preinstalled in the main Python +# installation. +# - In order to drop the root user, we have to make some directories world +# writable as OpenShift default security model is to run the container +# under random UID. +RUN python3.14 -m venv ${APP_ROOT} && \ +# Python 3.7+ only code, Python <3.7 installs pip from PyPI in the assemble script. \ +# We have to upgrade pip to a newer verison because \ +# pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 (and later) wheels \ +# support platforms like ppc64le, aarch64 or armv7 \ +# We are newly using wheel from one of the latest stable Fedora releases (from RPM python-pip-wheel) \ +# because it's tested better then whatever version from PyPI and contains useful patches. \ +# We have to do it here (in the macro) so the permissions are correctly fixed and pip is able \ +# to reinstall itself in the next build phases in the assemble script if user wants the latest version \ +${APP_ROOT}/bin/pip install /opt/wheels/pip-* && \ +rm -r /opt/wheels && \ +chown -R 1001:0 ${APP_ROOT} && \ +fix-permissions ${APP_ROOT} -P && \ +# The following echo adds the unset command for the variables set below to the \ +# venv activation script. This prevents the virtual environment from being \ +# activated multiple times and also every time the prompt is rendered. \ +echo "unset BASH_ENV PROMPT_COMMAND ENV" >> ${APP_ROOT}/bin/activate +# Ensure the virtualenv is activated in interactive shells +ENV BASH_ENV="${APP_ROOT}/bin/activate" \ + ENV="${APP_ROOT}/bin/activate" \ + PROMPT_COMMAND=". ${APP_ROOT}/bin/activate" + +USER 1001 + +# Set the default CMD to print the usage of the language image. +CMD $STI_SCRIPTS_PATH/usage diff --git a/3.14/README.md b/3.14/README.md new file mode 100644 index 00000000..67cddde8 --- /dev/null +++ b/3.14/README.md @@ -0,0 +1,386 @@ +Python 3.14 container image +========================= + +This container image includes Python 3.14 as a [S2I](https://github.com/openshift/source-to-image) base image for your Python 3.14 applications. +Users can choose between RHEL, CentOS Stream, and Fedora based builder images. +The RHEL images are available in the [Red Hat Container Catalog](https://catalog.redhat.com/software/containers/explore), +CentOS Stream images are available on [Quay.io](https://quay.io/organization/sclorg), +and the Fedora images are available on [Quay.io](https://quay.io/organization/fedora). +The resulting image can be run using [podman](https://github.com/containers/libpod) or +[docker](http://docker.io). + +Note: while the examples in this README are calling `podman`, you can replace any such calls by `docker` with the same arguments + +Description +----------- + +Python 3.14 available as container is a base platform for +building and running various Python 3.14 applications and frameworks. +Python is an easy to learn, powerful programming language. It has efficient high-level +data structures and a simple but effective approach to object-oriented programming. +Python's elegant syntax and dynamic typing, together with its interpreted nature, +make it an ideal language for scripting and rapid application development in many areas +on most platforms. + +This container image includes an npm utility +(see [base image repository](https://github.com/sclorg/s2i-base-container/tree/master/base)), +so users can use it to install JavaScript +modules for their web applications. There is no guarantee for any specific npm or nodejs +version, that is included in the image; those versions can be changed anytime and +the nodejs itself is included just to make the npm work. + +Usage in Openshift +------------------ + +For this, we will assume that you are using one of the supported images available via imagestream tags in Openshift, eg. `python:3.14-ubi9` +Building a simple [python-sample-app](https://github.com/sclorg/django-ex.git) application +in Openshift can be achieved with the following step: + + ``` + oc new-app python:3.14-ubi9~https://github.com/sclorg/django-ex.git + ``` + +**Accessing the application:** +``` +$ oc get pods +$ oc exec -- curl 127.0.0.1:8080 +``` + +Source-to-Image framework and scripts +------------------------------------- +This image supports the [Source-to-Image](https://docs.openshift.com/container-platform/4.14/openshift_images/create-images.html#images-create-s2i_create-images) +(S2I) strategy in OpenShift. The Source-to-Image is an OpenShift framework +which makes it easy to write images that take application source code as +an input, use a builder image like this Python container image, and produce +a new image that runs the assembled application as an output. + +To support the Source-to-Image framework, important scripts are included in the builder image: + +* The `/usr/libexec/s2i/assemble` script inside the image is run to produce a new image with the application artifacts. +The script takes sources of a given application and places them into appropriate directories inside the image. +It utilizes some common patterns in Perl application development (see the **Environment variables** section below). +* The `/usr/libexec/s2i/run` script is set as the default command in the resulting container image (the new image with the application artifacts). +It runs your application according to settings in `APP_MODULE`, `APP_FILE` or `APP_SCRIPT` environment variables or it tries to detect the best +way automatically. + +Building an application using a Dockerfile +------------------------------------------ +Compared to the Source-to-Image strategy, using a Dockerfile is a more +flexible way to build a Python container image with an application. +Use a Dockerfile when Source-to-Image is not sufficiently flexible for you or +when you build the image outside of the OpenShift environment. + +To use the Python image in a Dockerfile, follow these steps: +#### 1. Pull a base builder image to build on + +``` +podman pull quay.io/fedora/python-314 +``` + +#### 2. Pull and application code + +An example application available at https://github.com/sclorg/django-ex.git is used here. Feel free to clone the repository for further experiments. +You can also take a look at code examples in s2i-python-container repository: https://github.com/sclorg/s2i-python-container/tree/master/examples + +``` +git clone https://github.com/sclorg/django-ex.git app-src +``` + +#### 3. Prepare an application inside a container + +This step usually consists of at least these parts: + +* putting the application source into the container +* installing the dependencies +* setting the default command in the resulting image + +For all these three parts, users can either setup all manually and use commands `python` and `pip` explicitly in the Dockerfile, +or users can use the Source-to-Image scripts inside the image. + +The manual way comes with the highest level of flexibility but requires you to know how to work +with modules or software collections manually, how to setup virtual environment with the right version +of Python and many more. On the other hand, using Source-to-Image scripts makes your Dockerfile +prepared for a future flawless switch to a newer or different platform. + +To use the Source-to-Image scripts and build an image using a Dockerfile, create a Dockerfile with this content: + +``` +FROM quay.io/fedora/python-314 + +# Add application sources to a directory that the assemble script expects them +# and set permissions so that the container runs without root access +USER 0 +ADD app-src /tmp/src +RUN /usr/bin/fix-permissions /tmp/src +USER 1001 + +# Install the dependencies +RUN /usr/libexec/s2i/assemble + +# Set the default command for the resulting image +CMD /usr/libexec/s2i/run +``` + +If you decide not to use the Source-to-Image scripts, you will need to manually tailor the Dockerfile to your application and its needs. +Example Dockerfile for a simple Django application: + +``` +FROM quay.io/fedora/python-314 + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 +``` + +#### 4. Build a new image from a Dockerfile prepared in the previous step + +``` +podman build -t python-app . +``` + +#### 5. Run the resulting image with final application + +``` +podman run -d python-app +``` + +Environment variables +--------------------- + +To set these environment variables, you can place them as a key value pair into a `.s2i/environment` +file inside your source code repository. + +* **APP_SCRIPT** + + Used to run the application from a script file. + This should be a path to a script file (defaults to `app.sh` unless set to null) that will be + run to start the application. + +* **APP_FILE** + + Used to run the application from a Python script. + This should be a path to a Python file (defaults to `app.py` unless set to null) that will be + passed to the Python interpreter to start the application. + +* **APP_MODULE** + + Used to run the application with Gunicorn, as documented + [here](http://docs.gunicorn.org/en/latest/run.html#gunicorn). + This variable specifies a WSGI callable with the pattern + `MODULE_NAME:VARIABLE_NAME`, where `MODULE_NAME` is the full dotted path + of a module, and `VARIABLE_NAME` refers to a WSGI callable inside the + specified module. + Gunicorn will look for a WSGI callable named `application` if not specified. + + If `APP_MODULE` is not provided, the `run` script will look for a `wsgi.py` + file in your project and use it if it exists. + + If using `setup.py` for installing the application, the `MODULE_NAME` part + can be read from there. For an example, see + [setup-test-app](https://github.com/sclorg/s2i-python-container/tree/master/3.14/test/setup-test-app). + +* **APP_HOME** + + This variable can be used to specify a sub-directory in which the application to be run is contained. + The directory pointed to by this variable needs to contain `wsgi.py` (for Gunicorn) or `manage.py` (for Django). + + If `APP_HOME` is not provided, the `assemble` and `run` scripts will use the application's root + directory. + +* **APP_CONFIG** + + Path to a valid Python file with a + [Gunicorn configuration](http://docs.gunicorn.org/en/latest/configure.html#configuration-file) file. + +* **DISABLE_MIGRATE** + + Set this variable to a non-empty value to inhibit the execution of 'manage.py migrate' + when the produced image is run. This only affects Django projects. See + "Handling Database Migrations" section of [Django blogpost on OpenShift blog]( + https://blog.openshift.com/migrating-django-applications-openshift-3/) on suggestions + how/when to run DB migrations in OpenShift environment. Most importantly, + note that running DB migrations from two or more pods might corrupt your database. + +* **DISABLE_COLLECTSTATIC** + + Set this variable to a non-empty value to inhibit the execution of + 'manage.py collectstatic' during the build. This only affects Django projects. + +* **DISABLE_SETUP_PY_PROCESSING** / **DISABLE_PYPROJECT_TOML_PROCESSING** + + Set this to a non-empty value to skip processing of setup.{py,cfg} or pyproject.toml script if you + use `-e .` in requirements.txt to trigger its processing or you don't want + your application to be installed into site-packages directory. + +* **ENABLE_PIPENV** + + Set this variable to use [Pipenv](https://github.com/pypa/pipenv), + the higher-level Python packaging tool, to manage dependencies of the application. + This should be used only if your project contains properly formated Pipfile + and Pipfile.lock. + +* **PIN_PIPENV_VERSION** + + Set this variable together with `ENABLE_PIPENV` to use a specific version of Pipenv. + If not set, the latest stable version from PyPI is installed. + For example `PIN_PIPENV_VERSION=2018.11.26` installs `pipenv==2018.11.26`. + +* **ENABLE_MICROPIPENV** + + Set this variable to use [micropipenv](https://github.com/thoth-station/micropipenv), + a lightweight wrapper for pip to support requirements.txt, Pipenv and Poetry lock + files or converting them to pip-tools compatible output. Designed for containerized Python applications. + Available only for Python 3 images. + +* **ENABLE_INIT_WRAPPER** + + Set this variable to a non-empty value to make use of an init wrapper. + This is useful for servers that are not capable of reaping zombie + processes, such as Django development server or Tornado. This option can + be used together with **APP_SCRIPT** or **APP_FILE**. It never applies + to Gunicorn used through **APP_MODULE** as Gunicorn reaps zombie + processes correctly. + +* **PIP_INDEX_URL** + + Set this variable to use a custom index URL or mirror to download required + packages during build process. This affects packages listed in + requirements.txt. It also affects the installation of pipenv and + micropipenv and the update of pip in the container, though if not found in + the custom index, the container will try to install/update them from + upstream PyPI afterwards. + +* **PORT** + + HTTP(S) port your application should listen on. The default is 8080. + `PORT` is used only for Django development server and for Gunicorn + with the default configutation (no `APP_CONFIG` or `GUNICORN_CMD_ARGS` specified). + +* **UPGRADE_PIP_TO_LATEST** + + Set this variable to a non-empty value to have the 'pip' program and related + python packages (setuptools and wheel) be upgraded to the most recent version + before any Python packages are installed. If not set, the container will use + the stable pip version this container was built with, taken from a recent Fedora release. + +* **WEB_CONCURRENCY** + + Set this to change the default setting for the number of + [workers](http://docs.gunicorn.org/en/stable/settings.html#workers). By + default, this is set to the number of available cores times 2, capped + at 12. + + +Source repository layout +------------------------ + +You do not need to change anything in your existing Python project's repository. +However, if these files exist they will affect the behavior of the build process: + +* **requirements.txt** + + List of dependencies to be installed with `pip`. The format is documented + [here](https://pip.pypa.io/en/latest/user_guide.html#requirements-files). + + +* **Pipfile** + + The replacement for requirements.txt, project is currently under active + design and development, as documented [here](https://github.com/pypa/pipfile). + Set `ENABLE_PIPENV` environment variable to true in order to process this file. + + +* **setup.py** + + Configures various aspects of the project, including installation of + dependencies, as documented + [here](https://packaging.python.org/guides/distributing-packages-using-setuptools/?highlight=distributing#setup-py). + For most projects, it is sufficient to simply use `requirements.txt` or + `Pipfile`. Set `DISABLE_SETUP_PY_PROCESSING` environment variable to true + in order to skip processing of this file. + +Run strategies +-------------- + +The container image produced by s2i-python executes your project in one of the +following ways, in precedence order: + +* **Gunicorn** + + The Gunicorn WSGI HTTP server is used to serve your application in the case that it + is installed. It can be installed by listing it either in the `requirements.txt` + file or in the `install_requires` section of the `setup.py` file. + + If a file named `wsgi.py` is present in your repository, it will be used as + the entry point to your application. This can be overridden with the + environment variable `APP_MODULE`. + This file is present in Django projects by default. + + If you have both Django and Gunicorn in your requirements, your Django project + will automatically be served using Gunicorn. + + The default setting for Gunicorn (`--bind=0.0.0.0:$PORT --access-logfile=-`) is applied + only if both `$APP_CONFIG` and `$GUNICORN_CMD_ARGS` are not defined. + +* **Django development server** + + If you have Django in your requirements but don't have Gunicorn, then your + application will be served using Django's development web server. However, this is not + recommended for production environments. + +* **Python script** + + This would be used where you provide a Python code file for running you + application. It will be used in the case where you specify a path to a + Python script via the `APP_FILE` environment variable, defaulting to a + file named `app.py` if it exists. The script is passed to a regular + Python interpreter to launch your application. + +* **Application script file** + + This is the most general way of executing your application. It will be + used in the case where you specify a path to an executable script file + via the `APP_SCRIPT` environment variable, defaulting to a file named + `app.sh` if it exists. The script is executed directly to launch your + application. + +Hot deploy +---------- + +If you are using Django, hot deploy will work out of the box. + +To enable hot deploy while using Gunicorn, make sure you have a Gunicorn +configuration file inside your repository with the +[`reload`](https://gunicorn-docs.readthedocs.org/en/latest/settings.html#reload) +option set to `true`. Make sure to specify your config via the `APP_CONFIG` +environment variable. + +To change your source code in running container, use podman's (or docker's) +[exec](https://github.com/containers/podman/blob/main/docs/source/markdown/podman-exec.1.md) command: + +``` +podman exec -it /bin/bash +``` + +After you enter into the running container, your current directory is set +to `/opt/app-root/src`, where the source code is located. + + +See also +-------- +Dockerfile and other sources are available on https://github.com/sclorg/s2i-python-container. +In that repository you also can find another versions of Python environment Dockerfiles. +Dockerfile for RHEL8 is called `Dockerfile.rhel8`, for RHEL9 it's `Dockerfile.rhel9`, +for CentOS Stream 9 it's `Dockerfile.c9s`, for CentOS Stream 10 it's `Dockerfile.c10s`, +and the Fedora Dockerfile is called `Dockerfile.fedora`. diff --git a/3.14/root/opt/app-root/etc/generate_container_user b/3.14/root/opt/app-root/etc/generate_container_user new file mode 100644 index 00000000..a7fd74d6 --- /dev/null +++ b/3.14/root/opt/app-root/etc/generate_container_user @@ -0,0 +1,19 @@ +# Set current user in nss_wrapper +USER_ID=$(id -u) +GROUP_ID=$(id -g) + +if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"1001" ]; then + + NSS_WRAPPER_PASSWD=/opt/app-root/etc/passwd + NSS_WRAPPER_GROUP=/etc/group + + cat /etc/passwd | sed -e 's/^default:/builder:/' > $NSS_WRAPPER_PASSWD + + echo "default:x:${USER_ID}:${GROUP_ID}:Default Application User:${HOME}:/bin/bash" >> $NSS_WRAPPER_PASSWD + + export NSS_WRAPPER_PASSWD + export NSS_WRAPPER_GROUP + + LD_PRELOAD=libnss_wrapper.so + export LD_PRELOAD +fi diff --git a/3.14/root/opt/wheels b/3.14/root/opt/wheels new file mode 120000 index 00000000..4eabc067 --- /dev/null +++ b/3.14/root/opt/wheels @@ -0,0 +1 @@ +../../../src/root/opt/wheels/ \ No newline at end of file diff --git a/3.14/s2i/bin/assemble b/3.14/s2i/bin/assemble new file mode 100755 index 00000000..b5cfeb6f --- /dev/null +++ b/3.14/s2i/bin/assemble @@ -0,0 +1,134 @@ +#!/bin/bash + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_collectstatic() { + is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] +} + +function virtualenv_bin() { + # New versions of Python (>3.6) should use venv module + # from stdlib instead of virtualenv package + python3.14 -m venv $1 +} + +# Install pipenv or micropipenv to the separate virtualenv to isolate it +# from system Python packages and packages in the main +# virtualenv. Executable is simlinked into ~/.local/bin +# to be accessible. This approach is inspired by pipsi +# (pip script installer). +function install_tool() { + echo "---> Installing $1 packaging tool ..." + VENV_DIR=$HOME/.local/venvs/$1 + virtualenv_bin "$VENV_DIR" + # First, try to install the tool without --isolated which means that if you + # have your own PyPI mirror, it will take it from there. If this try fails, try it + # again with --isolated which ignores external pip settings (env vars, config file) + # and installs the tool from PyPI (needs internet connetion). + # $1$2 combines package name with [extras] or version specifier if is defined as $2``` + if ! $VENV_DIR/bin/pip install -U $1$2; then + echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" + $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` + fi + mkdir -p $HOME/.local/bin + ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 +} + +set -e + +# First of all, check that we don't have disallowed combination of ENVs +if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then + echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" + # podman/buildah does not relay this exit code but it will be fixed hopefuly + # https://github.com/containers/buildah/issues/2305 + exit 3 +fi + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* "$HOME" + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P + + +if [[ ! -z "$PIP_INDEX_URL" ]]; then + echo "---> Creating pip.ini config file" + echo "[global] +index-url = $PIP_INDEX_URL" >> /opt/app-root/src/pip.ini +fi + +if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then + echo "---> Upgrading pip, setuptools and wheel to latest version ..." + if ! pip install -U pip setuptools wheel; then + echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" + pip install --isolated -U pip setuptools wheel + fi +fi + +if [[ ! -z "$ENABLE_PIPENV" ]]; then + if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then + # Add == as a prefix to pipenv version, if defined + PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" + fi + install_tool "pipenv" "$PIN_PIPENV_VERSION" + echo "---> Installing dependencies via pipenv ..." + if [[ -f Pipfile ]]; then + pipenv install --deploy + elif [[ -f requirements.txt ]]; then + pipenv install -r requirements.txt + fi + # pipenv check +elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then + install_tool "micropipenv" "[toml]" + echo "---> Installing dependencies via micropipenv ..." + # micropipenv detects Pipfile.lock and requirements.txt in this order + micropipenv install --deploy +elif [[ -f requirements.txt ]]; then + echo "---> Installing dependencies ..." + pip install -r requirements.txt +fi + +if [[ ( -f setup.py || -f setup.cfg ) && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then + echo "---> Installing application (via setup.{py,cfg})..." + pip install . +fi + +if [[ -f pyproject.toml && -z "$DISABLE_PYPROJECT_TOML_PROCESSING" ]]; then + echo "---> Installing application (via pyproject.toml)..." + pip install . +fi + +if should_collectstatic; then + ( + echo "---> Collecting Django static files ..." + + APP_HOME=$(readlink -f "${APP_HOME:-.}") + # Change the working directory to APP_HOME + PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" + cd "$APP_HOME" + + # Look for 'manage.py' in the current directory + manage_file=./manage.py + + if [[ ! -f "$manage_file" ]]; then + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "'manage.py collectstatic' ignored." + exit + fi + + if ! python $manage_file collectstatic --dry-run --noinput &> /dev/null; then + echo "WARNING: could not run 'manage.py collectstatic'. To debug, run:" + echo " $ python $manage_file collectstatic --noinput" + echo "Ignore this warning if you're not serving static files with Django." + exit + fi + + python $manage_file collectstatic --noinput + ) +fi + +# set permissions for any installed artifacts +fix-permissions /opt/app-root -P diff --git a/3.14/s2i/bin/init-wrapper b/3.14/s2i/bin/init-wrapper new file mode 100755 index 00000000..26c8767d --- /dev/null +++ b/3.14/s2i/bin/init-wrapper @@ -0,0 +1,18 @@ +#!/bin/sh + +# Overview of how this script works: http://veithen.io/2014/11/16/sigterm-propagation.html +# Set a trap to kill the main app process when this +# init script receives SIGTERM or SIGINT +trap 'kill -s TERM $PID' TERM INT +# Execute the main application in the background +"$@" & +PID=$! +# wait command always terminates when trap is caught, even if the process hasn't finished yet +wait $PID +# Remove the trap and wait till the app process finishes completely +trap - TERM INT +# We wait again, since the first wait terminates when trap is caught +wait $PID +# Exit with the exit code of the app process +STATUS=$? +exit $STATUS diff --git a/3.14/s2i/bin/run b/3.14/s2i/bin/run new file mode 100755 index 00000000..7003f038 --- /dev/null +++ b/3.14/s2i/bin/run @@ -0,0 +1,155 @@ +#!/bin/bash +source /opt/app-root/etc/generate_container_user + +set -e + +function is_gunicorn_installed() { + hash gunicorn &>/dev/null +} + +function is_django_installed() { + python -c "import django" &>/dev/null +} + +function should_migrate() { + is_django_installed && [[ -z "$DISABLE_MIGRATE" ]] +} + +# Guess the number of workers according to the number of cores +function get_default_web_concurrency() { + limit_vars=$(cgroup-limits) + local $limit_vars + if [ -z "${NUMBER_OF_CORES:-}" ]; then + echo 1 + return + fi + + local max=$((NUMBER_OF_CORES*2)) + # Require at least 43 MiB and additional 40 MiB for every worker + local default=$(((${MEMORY_LIMIT_IN_BYTES:-MAX_MEMORY_LIMIT_IN_BYTES}/1024/1024 - 43) / 40)) + default=$((default > max ? max : default)) + default=$((default < 1 ? 1 : default)) + # According to http://docs.gunicorn.org/en/stable/design.html#how-many-workers, + # 12 workers should be enough to handle hundreds or thousands requests per second + default=$((default > 12 ? 12 : default)) + echo $default +} + +function maybe_run_in_init_wrapper() { + if [[ -z "$ENABLE_INIT_WRAPPER" ]]; then + exec "$@" + else + exec $STI_SCRIPTS_PATH/init-wrapper "$@" + fi +} + +# Look for gunicorn>=20.1.0 to utilize gunicorn.conf.py +if is_gunicorn_installed && [[ -f "gunicorn.conf.py" ]]; then + ret=$(python -c 'import gunicorn +ver = gunicorn.version_info +print(0 if ver[0]>=21 or (ver[0] == 20 and ver[1] >= 1) else 1)') + grep -q "wsgi_app" gunicorn.conf.py && grep_result=0 || grep_result=1 + if [[ $ret -eq 0 ]] && [[ $grep_result -eq 0 ]]; then + echo "---> Using gunicorn.conf.py" + echo "---> Serving application with gunicorn ..." + exec gunicorn + fi +fi + +APP_HOME=$(readlink -f "${APP_HOME:-.}") +# Change the working directory to APP_HOME +PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" +cd "$APP_HOME" + +if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then + # Set default values for APP_SCRIPT and APP_FILE only when all three APP_ + # variables are not defined by user. This prevents a situation when + # APP_MODULE is defined to app:application but the app.py file is found as the + # APP_FILE and then executed by Python instead of gunicorn. + APP_SCRIPT="app.sh" + APP_SCRIPT_DEFAULT=1 + APP_FILE="app.py" + APP_FILE_DEFAULT=1 +fi + +if [ ! -z "$APP_SCRIPT" ]; then + if [[ -f "$APP_SCRIPT" ]]; then + echo "---> Running application from script ($APP_SCRIPT) ..." + if [[ "$APP_SCRIPT" != /* ]]; then + APP_SCRIPT="./$APP_SCRIPT" + fi + maybe_run_in_init_wrapper "$APP_SCRIPT" + elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then + echo "ERROR: file '$APP_SCRIPT' not found." && exit 1 + fi +fi + +if [ ! -z "$APP_FILE" ]; then + if [[ -f "$APP_FILE" ]]; then + echo "---> Running application from Python script ($APP_FILE) ..." + maybe_run_in_init_wrapper python "$APP_FILE" + elif [[ -z "$APP_FILE_DEFAULT" ]]; then + echo "ERROR: file '$APP_FILE' not found." && exit 1 + fi +fi + +# Look for 'manage.py' in the current directory +manage_file=./manage.py + +if should_migrate; then + if [[ -f "$manage_file" ]]; then + echo "---> Migrating database ..." + python "$manage_file" migrate --noinput + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py migrate'." + fi +fi + +# If not set, use 8080 as the default port +if [ -z "$PORT" ]; then + PORT=8080 +fi + +if is_gunicorn_installed; then + setup_py=$(find "$HOME" -maxdepth 2 -type f -name 'setup.py' -print -quit) + # Look for wsgi module in the current directory + if [[ -z "$APP_MODULE" && -f "./wsgi.py" ]]; then + APP_MODULE=wsgi + elif [[ -z "$APP_MODULE" && -f "$setup_py" ]]; then + APP_MODULE="$(python "$setup_py" --name)" + fi + + if [[ "$APP_MODULE" ]]; then + export WEB_CONCURRENCY=${WEB_CONCURRENCY:-$(get_default_web_concurrency)} + + # Default settings for gunicorn if none of the custom are set + if [ -z "$APP_CONFIG" ] && [ -z "$GUNICORN_CMD_ARGS" ]; then + GUNICORN_CMD_ARGS="--bind=0.0.0.0:$PORT --access-logfile=-" + gunicorn_settings_source="default" + else + gunicorn_settings_source="custom" + fi + + # Gunicorn can read GUNICORN_CMD_ARGS as an env variable; we also pass them as + # arguments explicitly for compatibility with older Gunicorn versions. + echo "---> Serving application with gunicorn ($APP_MODULE) with $gunicorn_settings_source settings ..." + exec gunicorn "$APP_MODULE" $GUNICORN_CMD_ARGS --config "$APP_CONFIG" + fi +fi + +if is_django_installed; then + if [[ -f "$manage_file" ]]; then + echo "---> Serving application with 'manage.py runserver 0.0.0.0:$PORT' ..." + echo "WARNING: this is NOT a recommended way to run you application in production!" + echo "Consider using gunicorn or some other production web server." + maybe_run_in_init_wrapper python "$manage_file" runserver 0.0.0.0:$PORT + else + echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." + echo "Skipped 'python manage.py runserver'." + fi +fi + +>&2 echo "ERROR: don't know how to run your application." +>&2 echo "Please set either APP_MODULE, APP_FILE or APP_SCRIPT environment variables, or create a file 'app.py' to launch your application." +exit 1 diff --git a/3.14/s2i/bin/usage b/3.14/s2i/bin/usage new file mode 100755 index 00000000..9c6009c8 --- /dev/null +++ b/3.14/s2i/bin/usage @@ -0,0 +1,18 @@ +#!/bin/sh + +DISTRO=`cat /etc/*-release | grep ^ID= | grep -Po '".*?"' | tr -d '"'` +NAMESPACE=centos +[[ $DISTRO =~ rhel* ]] && NAMESPACE=rhscl + +cat < -- curl 127.0.0.1:8080 +EOF diff --git a/3.14/test/__init__.py b/3.14/test/__init__.py new file mode 120000 index 00000000..d0f4746a --- /dev/null +++ b/3.14/test/__init__.py @@ -0,0 +1 @@ +../../test/__init__.py \ No newline at end of file diff --git a/3.14/test/app-home-test-app b/3.14/test/app-home-test-app new file mode 120000 index 00000000..f6b42e77 --- /dev/null +++ b/3.14/test/app-home-test-app @@ -0,0 +1 @@ +../../examples/app-home-test-app \ No newline at end of file diff --git a/3.14/test/app-module-test-app b/3.14/test/app-module-test-app new file mode 120000 index 00000000..fa20034b --- /dev/null +++ b/3.14/test/app-module-test-app @@ -0,0 +1 @@ +../../examples/app-module-test-app \ No newline at end of file diff --git a/3.14/test/check_imagestreams.py b/3.14/test/check_imagestreams.py new file mode 120000 index 00000000..56bb2be7 --- /dev/null +++ b/3.14/test/check_imagestreams.py @@ -0,0 +1 @@ +../../common/check_imagestreams.py \ No newline at end of file diff --git a/3.14/test/conftest.py b/3.14/test/conftest.py new file mode 120000 index 00000000..3f2d7840 --- /dev/null +++ b/3.14/test/conftest.py @@ -0,0 +1 @@ +../../test/conftest.py \ No newline at end of file diff --git a/3.14/test/django-different-port-test-app b/3.14/test/django-different-port-test-app new file mode 120000 index 00000000..cc115af1 --- /dev/null +++ b/3.14/test/django-different-port-test-app @@ -0,0 +1 @@ +../../examples/django-different-port-test-app \ No newline at end of file diff --git a/3.14/test/django-test-app b/3.14/test/django-test-app new file mode 120000 index 00000000..52cebf3b --- /dev/null +++ b/3.14/test/django-test-app @@ -0,0 +1 @@ +../../examples/django-test-app \ No newline at end of file diff --git a/3.14/test/from-dockerfile/Dockerfile.tpl b/3.14/test/from-dockerfile/Dockerfile.tpl new file mode 120000 index 00000000..ba88ccc4 --- /dev/null +++ b/3.14/test/from-dockerfile/Dockerfile.tpl @@ -0,0 +1 @@ +../../../src/test/from-dockerfile/Dockerfile.tpl \ No newline at end of file diff --git a/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl b/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl new file mode 100644 index 00000000..da2f43ff --- /dev/null +++ b/3.14/test/from-dockerfile/Dockerfile_no_s2i.tpl @@ -0,0 +1,16 @@ +FROM #IMAGE_NAME# # Replaced by sed in ct_test_app_dockerfile + +# Add application sources with correct permissions for OpenShift +USER 0 +ADD app-src . +RUN chown -R 1001:0 ./ +USER 1001 + +# Install the dependencies +RUN pip install -U "pip>=19.3.1" && \ + pip install -r requirements.txt && \ + python manage.py collectstatic --noinput && \ + python manage.py migrate + +# Run the application +CMD python manage.py runserver 0.0.0.0:8080 diff --git a/3.14/test/gunicorn-config-different-port-test-app b/3.14/test/gunicorn-config-different-port-test-app new file mode 120000 index 00000000..c8eb6171 --- /dev/null +++ b/3.14/test/gunicorn-config-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-config-different-port-test-app \ No newline at end of file diff --git a/3.14/test/gunicorn-different-port-test-app b/3.14/test/gunicorn-different-port-test-app new file mode 120000 index 00000000..72c8e433 --- /dev/null +++ b/3.14/test/gunicorn-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-different-port-test-app \ No newline at end of file diff --git a/3.14/test/gunicorn-python-configfile-different-port-test-app b/3.14/test/gunicorn-python-configfile-different-port-test-app new file mode 120000 index 00000000..9139ee17 --- /dev/null +++ b/3.14/test/gunicorn-python-configfile-different-port-test-app @@ -0,0 +1 @@ +../../examples/gunicorn-python-configfile-different-port-test-app \ No newline at end of file diff --git a/3.14/test/imagestreams b/3.14/test/imagestreams new file mode 120000 index 00000000..7a0aee9c --- /dev/null +++ b/3.14/test/imagestreams @@ -0,0 +1 @@ +../../imagestreams \ No newline at end of file diff --git a/3.14/test/locale-test-app b/3.14/test/locale-test-app new file mode 120000 index 00000000..9105ec08 --- /dev/null +++ b/3.14/test/locale-test-app @@ -0,0 +1 @@ +../../examples/locale-test-app \ No newline at end of file diff --git a/3.14/test/micropipenv-requirements-test-app b/3.14/test/micropipenv-requirements-test-app new file mode 120000 index 00000000..cb2ddcf0 --- /dev/null +++ b/3.14/test/micropipenv-requirements-test-app @@ -0,0 +1 @@ +../../examples/micropipenv-requirements-test-app \ No newline at end of file diff --git a/3.14/test/micropipenv-test-app/.gitignore b/3.14/test/micropipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14/test/micropipenv-test-app/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/3.14/test/micropipenv-test-app/.s2i/environment b/3.14/test/micropipenv-test-app/.s2i/environment new file mode 100644 index 00000000..29efed4a --- /dev/null +++ b/3.14/test/micropipenv-test-app/.s2i/environment @@ -0,0 +1,5 @@ +ENABLE_MICROPIPENV=true +DISABLE_SETUP_PY_PROCESSING=true +# This tests second try to install micropipenv with --isolated +# because the first one won't work with following setting +PIP_INDEX_URL=https://example.com/ diff --git a/3.14/test/micropipenv-test-app/Pipfile b/3.14/test/micropipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14/test/micropipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14/test/micropipenv-test-app/Pipfile.lock b/3.14/test/micropipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14/test/micropipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "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.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14/test/micropipenv-test-app/setup.py b/3.14/test/micropipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14/test/micropipenv-test-app/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + + +setup ( + name = "testapp", + version = "0.1", + description = "Example application to be deployed.", + packages = find_packages(), + install_requires = ["gunicorn"], +) diff --git a/3.14/test/micropipenv-test-app/testapp.py b/3.14/test/micropipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14/test/micropipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14/test/mod-wsgi-test-app b/3.14/test/mod-wsgi-test-app new file mode 120000 index 00000000..9d8c3338 --- /dev/null +++ b/3.14/test/mod-wsgi-test-app @@ -0,0 +1 @@ +../../examples/mod-wsgi-test-app \ No newline at end of file diff --git a/3.14/test/npm-virtualenv-uwsgi-test-app b/3.14/test/npm-virtualenv-uwsgi-test-app new file mode 120000 index 00000000..b34ae21e --- /dev/null +++ b/3.14/test/npm-virtualenv-uwsgi-test-app @@ -0,0 +1 @@ +../../examples/npm-virtualenv-uwsgi-test-app \ No newline at end of file diff --git a/3.14/test/numpy-test-app b/3.14/test/numpy-test-app new file mode 120000 index 00000000..b064f87f --- /dev/null +++ b/3.14/test/numpy-test-app @@ -0,0 +1 @@ +../../examples/numpy-test-app \ No newline at end of file diff --git a/3.14/test/pin-pipenv-version-test-app/.s2i/environment b/3.14/test/pin-pipenv-version-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.14/test/pin-pipenv-version-test-app/app.sh b/3.14/test/pin-pipenv-version-test-app/app.sh new file mode 100755 index 00000000..4334c760 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/app.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# Test the uwsgi server +exec uwsgi \ + --http-socket :8080 \ + --die-on-term \ + --master \ + --single-interpreter \ + --enable-threads \ + --threads=5 \ + --thunder-lock \ + --module wsgi diff --git a/3.14/test/pin-pipenv-version-test-app/requirements.txt b/3.14/test/pin-pipenv-version-test-app/requirements.txt new file mode 100644 index 00000000..66d42051 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/requirements.txt @@ -0,0 +1,2 @@ +uWSGI +Flask diff --git a/3.14/test/pin-pipenv-version-test-app/wsgi.py b/3.14/test/pin-pipenv-version-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14/test/pin-pipenv-version-test-app/wsgi.py @@ -0,0 +1,9 @@ +from flask import Flask +application = Flask(__name__) + +@application.route('/') +def hello(): + return b'Hello World from uWSGI hosted WSGI application!' + +if __name__ == '__main__': + application.run() diff --git a/3.14/test/pipenv-and-micropipenv-should-fail-test-app b/3.14/test/pipenv-and-micropipenv-should-fail-test-app new file mode 120000 index 00000000..cf830645 --- /dev/null +++ b/3.14/test/pipenv-and-micropipenv-should-fail-test-app @@ -0,0 +1 @@ +../../src/test/pipenv-and-micropipenv-should-fail-test-app \ No newline at end of file diff --git a/3.14/test/pipenv-test-app/.gitignore b/3.14/test/pipenv-test-app/.gitignore new file mode 100644 index 00000000..ba746605 --- /dev/null +++ b/3.14/test/pipenv-test-app/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/3.14/test/pipenv-test-app/.s2i/environment b/3.14/test/pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..4a4f7605 --- /dev/null +++ b/3.14/test/pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=true +DISABLE_SETUP_PY_PROCESSING=true diff --git a/3.14/test/pipenv-test-app/Pipfile b/3.14/test/pipenv-test-app/Pipfile new file mode 100644 index 00000000..45aee705 --- /dev/null +++ b/3.14/test/pipenv-test-app/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +"e1839a8" = {path = ".", editable = true} +requests = "==2.28.2" +setuptools = {version = "==67.7.2", markers="python_version >= '3.12'"} + +[dev-packages] +pytest = ">=2.8.0" + +[requires] +python_version = "3.14" diff --git a/3.14/test/pipenv-test-app/Pipfile.lock b/3.14/test/pipenv-test-app/Pipfile.lock new file mode 100644 index 00000000..11c134ec --- /dev/null +++ b/3.14/test/pipenv-test-app/Pipfile.lock @@ -0,0 +1,242 @@ +{ + "_meta": { + "hash": { + "sha256": "97bbfa6552887882d1faa4fdebfc913bed2b4c622dd7d6ebf8d7a72155e29b4d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.14" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", + "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.1.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "gunicorn": { + "hashes": [ + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "requests": { + "hashes": [ + "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", + "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.2" + }, + "setuptools": { + "hashes": [ + "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b", + "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990" + ], + "markers": "python_version >= '3.12'", + "version": "==67.7.2" + }, + "testapp": { + "version": "==0.1" + }, + "urllib3": { + "hashes": [ + "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", + "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32" + ], + "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.20" + } + }, + "develop": { + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", + "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.0.2" + } + } +} diff --git a/3.14/test/pipenv-test-app/setup.py b/3.14/test/pipenv-test-app/setup.py new file mode 100644 index 00000000..e3134094 --- /dev/null +++ b/3.14/test/pipenv-test-app/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + + +setup ( + name = "testapp", + version = "0.1", + description = "Example application to be deployed.", + packages = find_packages(), + install_requires = ["gunicorn"], +) diff --git a/3.14/test/pipenv-test-app/testapp.py b/3.14/test/pipenv-test-app/testapp.py new file mode 100644 index 00000000..fe200352 --- /dev/null +++ b/3.14/test/pipenv-test-app/testapp.py @@ -0,0 +1,12 @@ +import sys +import requests + + +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + # Python 3.12 needed requests version bump + if sys.version_info.major == 3 and sys.version_info.minor >= 12: + assert requests.__version__ == '2.28.2' + else: + assert requests.__version__ == '2.20.0' + return [b"Hello from gunicorn WSGI application!"] diff --git a/3.14/test/poetry-src-layout-test-app b/3.14/test/poetry-src-layout-test-app new file mode 120000 index 00000000..92d2990d --- /dev/null +++ b/3.14/test/poetry-src-layout-test-app @@ -0,0 +1 @@ +../../examples/poetry-src-layout-test-app \ No newline at end of file diff --git a/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment b/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment new file mode 100644 index 00000000..160e51c4 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/.s2i/environment @@ -0,0 +1,2 @@ +ENABLE_PIPENV=1 +PIN_PIPENV_VERSION=2023.11.14 diff --git a/3.14/test/pyuwsgi-pipenv-test-app/app.sh b/3.14/test/pyuwsgi-pipenv-test-app/app.sh new file mode 100755 index 00000000..3a2680f2 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/app.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source .s2i/environment + +echo "Testing PIN_PIPENV_VERSION (set in .s2i/environment) ..." +pipenv_version=`pipenv --version` +expected="pipenv, version $PIN_PIPENV_VERSION" +if [ "$pipenv_version" != "$expected" ]; then + echo "ERROR: pipenv version is different than expected." + echo "Expected: ${expected}" + echo "Actual: ${pipenv_version}" + exit 1 +fi + +# Test the uwsgi server +exec uwsgi \ + --http-socket :8080 \ + --die-on-term \ + --master \ + --single-interpreter \ + --enable-threads \ + --threads=5 \ + --thunder-lock \ + --module wsgi diff --git a/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt b/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt new file mode 100644 index 00000000..932ba1d6 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/requirements.txt @@ -0,0 +1,2 @@ +pyuwsgi # this is just a copy of uwsgi project but it has wheels on PyPI +Flask diff --git a/3.14/test/pyuwsgi-pipenv-test-app/wsgi.py b/3.14/test/pyuwsgi-pipenv-test-app/wsgi.py new file mode 100644 index 00000000..96e1a614 --- /dev/null +++ b/3.14/test/pyuwsgi-pipenv-test-app/wsgi.py @@ -0,0 +1,9 @@ +from flask import Flask +application = Flask(__name__) + +@application.route('/') +def hello(): + return b'Hello World from uWSGI hosted WSGI application!' + +if __name__ == '__main__': + application.run() diff --git a/3.14/test/run b/3.14/test/run new file mode 100755 index 00000000..134639eb --- /dev/null +++ b/3.14/test/run @@ -0,0 +1,312 @@ +#!/bin/bash +# +# The 'run' performs a simple test that verifies that S2I image. +# The main focus here is to excersise the S2I scripts. +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# +declare -a COMMON_WEB_APPS=({gunicorn-config-different-port,gunicorn-different-port,django-different-port,standalone,setup,setup-requirements,django,numpy,app-home,locale,pipenv,pipenv-and-micropipenv-should-fail,app-module,pyuwsgi-pipenv,micropipenv,standalone-custom-pypi-index,gunicorn-python-configfile-different-port}-test-app) +declare -a FULL_WEB_APPS=({setup-cfg,npm-virtualenv-uwsgi,mod-wsgi,pin-pipenv-version,micropipenv-requirements,poetry-src-layout}-test-app) +declare -a MINIMAL_WEB_APPS=() +declare -a WEB_APPS=(${COMMON_WEB_APPS[@]} ${FULL_WEB_APPS[@]}) + +# Some tests, like the one using the latest pipenv, might be unstable +# because new upstream releases tend to break our tests sometimes. +# If a test is in UNSTABLE_TESTS and IGNORE_UNSTABLE_TESTS env +# variable is defined, a result of the test has no impact on +# the overall result of the test suite. +# +# Reasons for specific tests to be marked as unstable: +# pipenv-test-app: +# - this testcase installs pipenv from the internet. +# Problem is that the upstream releases are from time-to-time broken +# which breaks this test. We generally want to know about it +# in upstream, but ignore this in downstream. +declare -a UNSTABLE_TESTS=(pipenv-test-app) + +# TODO: Make command compatible for Mac users +test_dir="$(readlink -f $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$(readlink -f ${test_dir}/..) + +TEST_LIST="\ +test_s2i_usage +test_docker_run_usage +test_application +test_application_with_user +test_application_enable_init_wrapper +" + +TEST_VAR_DOCKER="\ +test_from_dockerfile +" + +if [[ -z $VERSION ]]; then + echo "ERROR: The VERSION variable must be set." + ct_check_testcase_result 1 + exit 1 +fi + +IMAGE_NAME=${IMAGE_NAME:-ubi9/python-${VERSION//./}} + +. test/test-lib.sh + +info() { + echo -e "\n\e[1m[INFO] $@\e[0m\n" +} + +image_exists() { + docker inspect $1 &>/dev/null +} + +container_exists() { + image_exists $(cat $cid_file) +} + + +container_ip() { + docker inspect --format="{{ .NetworkSettings.IPAddress }}" $(cat $cid_file) +} + +run_s2i_build() { + info "Building the ${1} application image ..." + ct_s2i_build_as_df file://${test_dir}/${1} ${IMAGE_NAME} ${IMAGE_NAME}-testapp ${s2i_args} +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + return 1 + fi + # TODO: S2I build require the application is a valid 'GIT' repository, we + # should remove this restriction in the future when a file:// is used. + info "Preparing to test ${1} ..." + pushd ${test_dir}/${1} >/dev/null + git init + git config user.email "build@localhost" && git config user.name "builder" + git add -A && git commit -m "Sample commit" + popd >/dev/null +} + +run_test_application() { + docker run --user=100001 ${CONTAINER_ARGS} --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp +} + +cleanup() { + info "Cleaning up the test application image" + if image_exists ${IMAGE_NAME}-testapp; then + docker rmi -f ${IMAGE_NAME}-testapp + fi + rm -rf ${test_dir}/${1}/.git +} +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + info "Waiting for application container to start $CONTAINER_ARGS ..." + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && [ -s $cid_file ] && return 0 + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return 1 +} + +test_s2i_usage() { + info "Testing 's2i usage' ..." + ct_s2i_usage ${IMAGE_NAME} ${s2i_args} 1>/dev/null +} + +test_docker_run_usage() { + info "Testing 'docker run' usage ..." + docker run --rm ${IMAGE_NAME} &>/dev/null +} + +test_scl_usage() { + local run_cmd="$1" + local expected="$2" + local cid_file="$3" + + info "Testing command availability in the image" + out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[/bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/bash -c "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi + out=$(docker exec $(cat ${cid_file}) /bin/sh -ic "${run_cmd}" 2>&1) + if ! echo "${out}" | grep -q "${expected}"; then + echo "ERROR[exec /bin/sh -ic "${run_cmd}"] Expected '${expected}', got '${out}'" + return 1 + fi +} + +test_connection() { + info "Testing the HTTP connection (http://$(container_ip):${test_port}) ${CONTAINER_ARGS} ..." + local max_attempts=30 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + response_code=$(curl -s -w %{http_code} -o /dev/null http://$(container_ip):${test_port}/) + status=$? + if [ $status -eq 0 ]; then + if [ $response_code -eq 200 ]; then + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +test_application() { + local cid_file="$CID_FILE_DIR"/"$(mktemp -u -p . --suffix .cid)" + # Verify that the HTTP connection can be established to test application container + run_test_application & + + # Wait for the container to write it's CID file + wait_for_cid + # Some test apps have tests in their startup code so we have to check + # that the container starts at all + ct_check_testcase_result $? + + # Instead of relying on VERSION variable coming from Makefile + # set the expected string based on the PYTHON_VERSION defined + # inside the running container. + python_version=$(docker run --rm $IMAGE_NAME /bin/bash -c "echo \$PYTHON_VERSION" 2>&1) + + test_scl_usage "python --version" "Python $python_version." "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "node --version" "^v[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_scl_usage "npm --version" "^[0-9]*\.[0-9]*\.[0-9]*" "${cid_file}" + ct_check_testcase_result $? + test_connection + ct_check_testcase_result $? + container_exists && docker stop $(cat "$cid_file") +} + +test_from_dockerfile(){ + info "Test from Dockerfile" + # Django 4.2 supports Python 3.9+ + django_example_repo_url="https://github.com/sclorg/django-ex.git@4.2.x" + + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? + + info "Test from Dockerfile with no s2i scripts used" + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile_no_s2i.tpl $django_example_repo_url 'Welcome to your Django application on OpenShift' app-src + ct_check_testcase_result $? +} + +test_from_dockerfile_minimal(){ + info "Test from Dockerfile" + + # The following tests are for multi-stage builds. These technically also work on full images, but there is no reason to do multi-stage builds with full images. + + # uwsgi in uwsgi-test-app + ct_test_app_dockerfile $test_dir/from-dockerfile/uwsgi.Dockerfile.tpl $test_dir/uwsgi-test-app 'Hello World from uWSGI hosted WSGI application!' app-src + ct_check_testcase_result $? + + # So far, for all the minimal images, the name of the full container image counterpart + # is the same just without -minimal infix. + # sclorg/python-39-minimal-c9s / sclorg/python-39-c9s + # ubi8/python-39-minimal / ubi8/python-39 + FULL_IMAGE_NAME=${IMAGE_NAME/-minimal/} + + if ct_pull_image "$FULL_IMAGE_NAME"; then + # mod_wsgi in micropipenv-requirements-test-app + sed "s@#IMAGE_NAME#@${IMAGE_NAME}@;s@#FULL_IMAGE_NAME#@${FULL_IMAGE_NAME}@" $test_dir/from-dockerfile/mod_wsgi.Dockerfile.tpl > $test_dir/from-dockerfile/Dockerfile + ct_test_app_dockerfile $test_dir/from-dockerfile/Dockerfile $test_dir/micropipenv-requirements-test-app 'Hello World from mod_wsgi hosted WSGI application!' app-src + ct_check_testcase_result $? + else + echo "[SKIP] Multistage build from Dockerfile - $FULL_IMAGE_NAME does not exists." + fi + +} + +test_application_with_user() { + # test application with random user + CONTAINER_ARGS="--user 12345" test_application + +} + +test_application_enable_init_wrapper() { + # test application with init wrapper + CONTAINER_ARGS="-e ENABLE_INIT_WRAPPER=true" test_application +} + +# Positive test & non-zero exit status = ERROR. +# Negative test & zero exit status = ERROR. +# Tests with '-should-fail-' in their name should fail during a build, +# expecting non-zero exit status. +evaluate_build_result() { + local _result="$1" + local _app="$2" + local _type="positive" + local _test_msg="[PASSED]" + local _ret_code=0 + + if [[ "$_app" == *"-should-fail-"* ]]; then + _type="negative" + fi + + if [[ "$_type" == "positive" && "$_result" != "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED:0 GOT:${_result}" + _ret_code=$_result + elif [[ "$_type" == "negative" && "$_result" == "0" ]]; then + info "TEST FAILED (${_type}), EXPECTED: non-zero GOT:${_result}" + _ret_code=1 + fi + if [ $_ret_code != 0 ]; then + cleanup + TESTSUITE_RESULT=1 + _test_msg="[FAILED]" + fi + ct_update_test_result "$_test_msg" "$_app" run_s2i_build + + if [[ "$_type" == "negative" && "$_result" != "0" ]]; then + _ret_code=127 # even though this is success, the app is still not built + fi + return $_ret_code +} + +ct_init + +# For debugging purposes, this script can be run with one or more arguments +# those arguments list is a sub-set of values in the WEB_APPS array defined above +# Example: ./run app-home-test-app pipenv-test-app +for app in ${@:-${WEB_APPS[@]}}; do + # Since we built the candidate image locally, we don't want S2I attempt to pull + # it from Docker hub + s2i_args="--pull-policy=never" + + # Example apps with "-different-port-" in their name don't use the default port 8080 + if [[ "$app" == *"-different-port-"* ]]; then + test_port=8085 + else + test_port=8080 + fi + + prepare ${app} + if [ $? -ne 0 ]; then + ct_update_test_result "[FAILED]" "${app}" "preparation" + TESTSUITE_RESULT=1 + continue + fi + run_s2i_build ${app} + evaluate_build_result $? "$app" || continue + + TEST_SET=${TESTS:-$TEST_LIST} ct_run_tests_from_testset "${app}" + + cleanup ${app} +done + +TEST_SET=${TESTS:-$TEST_VAR_DOCKER} ct_run_tests_from_testset "var-docker" diff --git a/3.14/test/run-openshift-pytest b/3.14/test/run-openshift-pytest new file mode 120000 index 00000000..5063ae30 --- /dev/null +++ b/3.14/test/run-openshift-pytest @@ -0,0 +1 @@ +../../test/run-openshift-pytest \ No newline at end of file diff --git a/3.14/test/run-openshift-remote-cluster b/3.14/test/run-openshift-remote-cluster new file mode 120000 index 00000000..1bffcba8 --- /dev/null +++ b/3.14/test/run-openshift-remote-cluster @@ -0,0 +1 @@ +../../test/run-openshift-remote-cluster \ No newline at end of file diff --git a/3.14/test/run-pytest b/3.14/test/run-pytest new file mode 100755 index 00000000..e69959ad --- /dev/null +++ b/3.14/test/run-pytest @@ -0,0 +1,15 @@ +#!/bin/bash +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# SINGLE_VERSION specifies the major version of the MariaDB in format of X.Y +# OS specifies RHEL version (e.g. OS=rhel8) +# + +THISDIR=$(dirname ${BASH_SOURCE[0]}) + +PYTHON_VERSION="3.12" +if [[ ! -f "/usr/bin/python$PYTHON_VERSION" ]]; then + PYTHON_VERSION="3.13" +fi +cd "${THISDIR}" && "python${PYTHON_VERSION}" -m pytest -s -rA --showlocals -vv test_container_*.py diff --git a/3.14/test/setup-cfg-test-app b/3.14/test/setup-cfg-test-app new file mode 120000 index 00000000..71a2b0cb --- /dev/null +++ b/3.14/test/setup-cfg-test-app @@ -0,0 +1 @@ +../../examples/setup-cfg-test-app \ No newline at end of file diff --git a/3.14/test/setup-requirements-test-app b/3.14/test/setup-requirements-test-app new file mode 120000 index 00000000..c909ccb9 --- /dev/null +++ b/3.14/test/setup-requirements-test-app @@ -0,0 +1 @@ +../../examples/setup-requirements-test-app \ No newline at end of file diff --git a/3.14/test/setup-test-app b/3.14/test/setup-test-app new file mode 120000 index 00000000..1b49f26f --- /dev/null +++ b/3.14/test/setup-test-app @@ -0,0 +1 @@ +../../examples/setup-test-app \ No newline at end of file diff --git a/3.14/test/standalone-custom-pypi-index-test-app b/3.14/test/standalone-custom-pypi-index-test-app new file mode 120000 index 00000000..12941a9a --- /dev/null +++ b/3.14/test/standalone-custom-pypi-index-test-app @@ -0,0 +1 @@ +../../examples/standalone-custom-pypi-index-test-app \ No newline at end of file diff --git a/3.14/test/standalone-test-app b/3.14/test/standalone-test-app new file mode 120000 index 00000000..3f158948 --- /dev/null +++ b/3.14/test/standalone-test-app @@ -0,0 +1 @@ +../../examples/standalone-test-app \ No newline at end of file diff --git a/3.14/test/test-lib-openshift.sh b/3.14/test/test-lib-openshift.sh new file mode 120000 index 00000000..4f9f2996 --- /dev/null +++ b/3.14/test/test-lib-openshift.sh @@ -0,0 +1 @@ +../../common/test-lib-openshift.sh \ No newline at end of file diff --git a/3.14/test/test-lib-python.sh b/3.14/test/test-lib-python.sh new file mode 120000 index 00000000..0fa72132 --- /dev/null +++ b/3.14/test/test-lib-python.sh @@ -0,0 +1 @@ +../../test/test-lib-python.sh \ No newline at end of file diff --git a/3.14/test/test-lib-remote-openshift.sh b/3.14/test/test-lib-remote-openshift.sh new file mode 120000 index 00000000..92ad2f4d --- /dev/null +++ b/3.14/test/test-lib-remote-openshift.sh @@ -0,0 +1 @@ +../../common/test-lib-remote-openshift.sh \ No newline at end of file diff --git a/3.14/test/test-lib.sh b/3.14/test/test-lib.sh new file mode 120000 index 00000000..1ac99b93 --- /dev/null +++ b/3.14/test/test-lib.sh @@ -0,0 +1 @@ +../../common/test-lib.sh \ No newline at end of file diff --git a/3.14/test/test-openshift.yaml b/3.14/test/test-openshift.yaml new file mode 120000 index 00000000..8613fbba --- /dev/null +++ b/3.14/test/test-openshift.yaml @@ -0,0 +1 @@ +../../common/test-openshift.yaml \ No newline at end of file diff --git a/3.14/test/test_container_application.py b/3.14/test/test_container_application.py new file mode 120000 index 00000000..25c1c1ec --- /dev/null +++ b/3.14/test/test_container_application.py @@ -0,0 +1 @@ +../../test/test_container_application.py \ No newline at end of file diff --git a/3.14/test/test_container_basics.py b/3.14/test/test_container_basics.py new file mode 120000 index 00000000..b85dc139 --- /dev/null +++ b/3.14/test/test_container_basics.py @@ -0,0 +1 @@ +../../test/test_container_basics.py \ No newline at end of file diff --git a/3.14/test/test_ocp_deploy_templates.py b/3.14/test/test_ocp_deploy_templates.py new file mode 120000 index 00000000..0b821390 --- /dev/null +++ b/3.14/test/test_ocp_deploy_templates.py @@ -0,0 +1 @@ +../../test/test_ocp_deploy_templates.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_django_app.py b/3.14/test/test_ocp_helm_python_django_app.py new file mode 120000 index 00000000..34b4f0dd --- /dev/null +++ b/3.14/test/test_ocp_helm_python_django_app.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_app.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_django_psql_persistent.py b/3.14/test/test_ocp_helm_python_django_psql_persistent.py new file mode 120000 index 00000000..64d2105d --- /dev/null +++ b/3.14/test/test_ocp_helm_python_django_psql_persistent.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_django_psql_persistent.py \ No newline at end of file diff --git a/3.14/test/test_ocp_helm_python_imagestreams.py b/3.14/test/test_ocp_helm_python_imagestreams.py new file mode 120000 index 00000000..0022607f --- /dev/null +++ b/3.14/test/test_ocp_helm_python_imagestreams.py @@ -0,0 +1 @@ +../../test/test_ocp_helm_python_imagestreams.py \ No newline at end of file diff --git a/3.14/test/test_ocp_imagestreams_quickstart.py b/3.14/test/test_ocp_imagestreams_quickstart.py new file mode 120000 index 00000000..18f8d7e2 --- /dev/null +++ b/3.14/test/test_ocp_imagestreams_quickstart.py @@ -0,0 +1 @@ +../../test/test_ocp_imagestreams_quickstart.py \ No newline at end of file diff --git a/3.14/test/test_ocp_python_ex_standalone.py b/3.14/test/test_ocp_python_ex_standalone.py new file mode 120000 index 00000000..bb6022f7 --- /dev/null +++ b/3.14/test/test_ocp_python_ex_standalone.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_standalone.py \ No newline at end of file diff --git a/3.14/test/test_ocp_python_ex_template.py b/3.14/test/test_ocp_python_ex_template.py new file mode 120000 index 00000000..b75080ca --- /dev/null +++ b/3.14/test/test_ocp_python_ex_template.py @@ -0,0 +1 @@ +../../test/test_ocp_python_ex_template.py \ No newline at end of file