diff options
-rw-r--r-- | Pipfile | 18 | ||||
-rw-r--r-- | Pipfile.lock | 267 | ||||
-rw-r--r-- | README.md | 58 | ||||
-rw-r--r-- | config.py | 35 | ||||
-rw-r--r-- | docker-compose.yml | 40 | ||||
-rw-r--r-- | docker/Dockerfile | 3 | ||||
-rw-r--r-- | docker/Dockerfile.webapp | 25 | ||||
-rw-r--r-- | rmq.py | 113 | ||||
-rw-r--r-- | snekbox.py | 64 | ||||
-rw-r--r-- | snekweb.py | 74 | ||||
-rw-r--r-- | templates/index.html | 114 | ||||
-rw-r--r-- | templates/result.html | 9 | ||||
-rw-r--r-- | tests/test_snekbox.py | 20 |
13 files changed, 146 insertions, 694 deletions
@@ -4,15 +4,10 @@ verify_ssl = true name = "pypi" [packages] -pika = "*" -docker = "*" - -[dev-packages] flask = "*" -flask-sockets = "*" -gevent = "==1.2.2" -gevent-websocket = "*" gunicorn = "*" + +[dev-packages] pytest = "*" pytest-cov = "*" pytest-dependency = "*" @@ -32,13 +27,10 @@ python_version = "3.6" [scripts] lint = "flake8" precommit = "pre-commit install" -test = "py.test tests --cov . --cov-report term-missing -v" -report = "py.test tests --cov . --cov-report=html" -snekbox = "python snekbox.py" -snekweb = "gunicorn -w 2 -b 0.0.0.0:5000 --log-level debug -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker snekweb:app" +test = "pytest tests --cov . --cov-report term-missing -v" +report = "pytest tests --cov . --cov-report=html" +snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox:app" buildbox = "docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile ." pushbox = "docker push pythondiscord/snekbox:latest" buildboxbase = "docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile ." pushboxbase = "docker push pythondiscord/snekbox-base:latest" -buildweb = "docker build -t pythondiscord/snekboxweb:latest -f docker/Dockerfile.webapp ." -pushweb = "docker push pythondiscord/snekboxweb:latest" diff --git a/Pipfile.lock b/Pipfile.lock index 11a6201..358c6c0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0c682a13bfe227f8f95421c69f8d68b6fca2d4a841a45688f2cac078a97b1db1" + "sha256": "fe7027dedd12b67ee1b1f6a38e18184e8c3a77479b3ef564cce983d6816dc10d" }, "pipfile-spec": 6, "requires": { @@ -16,77 +16,82 @@ ] }, "default": { - "certifi": { - "hashes": [ - "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", - "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" - ], - "version": "==2019.3.9" - }, - "chardet": { + "click": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "version": "==3.0.4" + "version": "==7.0" }, - "docker": { + "flask": { "hashes": [ - "sha256:0076504c42b6a671c8e7c252913f59852669f5f882522f4d320ec7613b853553", - "sha256:d2c14d2cc7d54818897cc6f3cf73923c4e7dfe12f08f7bddda9dbea7fa82ea36" + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" ], "index": "pypi", - "version": "==3.7.1" - }, - "docker-pycreds": { - "hashes": [ - "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", - "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49" - ], - "version": "==0.4.0" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" + "version": "==1.0.2" }, - "pika": { + "gunicorn": { "hashes": [ - "sha256:b0640085f1d6398fd47bb16a17713053e26578192821ea5d928772b8e6a28789", - "sha256:b785e0d5f74a94781bd7d020862eb137d2b56cef2a21475aadbe5bcc8ec4db15" + "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", + "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" ], "index": "pypi", - "version": "==0.13.1" + "version": "==19.9.0" }, - "requests": { + "itsdangerous": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], - "version": "==2.21.0" + "version": "==1.1.0" }, - "six": { + "jinja2": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" ], - "version": "==1.12.0" + "version": "==2.10" }, - "urllib3": { + "markupsafe": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "version": "==1.24.1" + "version": "==1.1.1" }, - "websocket-client": { + "werkzeug": { "hashes": [ - "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", - "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" + "sha256:96da23fa8ccecbc3ae832a83df5c722c11547d021637faacb0bec4dd2f4666c8", + "sha256:ca5c2dcd367d6c0df87185b9082929d255358f5391923269335782b213d52655" ], - "version": "==0.56.0" + "version": "==0.15.1" } }, "develop": { @@ -118,13 +123,6 @@ ], "version": "==1.5.0" }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, "coverage": { "hashes": [ "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", @@ -178,11 +176,11 @@ }, "flake8-bugbear": { "hashes": [ - "sha256:07b6e769d7f4e168d590f7088eae40f6ddd9fa4952bed31602def65842682c83", - "sha256:0ccf56975f4db1d69dc1cf3598c99d768ebf95d0cad27d76087954aa399b515a" + "sha256:5070774b668be92c4312e5ca82748ddf4ecaa7a24ff062662681bb745c7896eb", + "sha256:fef9c9826d14ec23187ae1edeb3c6513c4e46bf0e70d86bac38f7d9aabae113d" ], "index": "pypi", - "version": "==18.8.0" + "version": "==19.3.0" }, "flake8-docstrings": { "hashes": [ @@ -238,95 +236,12 @@ "index": "pypi", "version": "==0.7" }, - "flask": { - "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "flask-sockets": { - "hashes": [ - "sha256:072927da8edca0e81e024f5787e643c87d80b351b714de95d723becb30e0643b", - "sha256:350a76d55f5889f64afd2ca9b32f262680b7960965f0830365576307d30cfe1e" - ], - "index": "pypi", - "version": "==0.2.1" - }, - "gevent": { - "hashes": [ - "sha256:0901975628790e8a57fc92bb7062e5b856edea48c8de9caf36cfda14eae07329", - "sha256:1af93825db5753550fa8ff5ab2f2132e8733170b3f8d38347b34fa4a984cb624", - "sha256:2ff045a91509c35664c27a849c8cbf742a227f587b7cdbc88301e9c85dcaedff", - "sha256:35790f1a3c8e431ada3471b70bb2105050009ea4beb15cbe41b86bc716a7ffa9", - "sha256:4791c8ae9c57d6f153354736e1ccab1e2baf6c8d9ae5a77a9ac90f41e2966b2d", - "sha256:552719cec4721673b8c7d2f9de666e3f7591b9b182f801ecaef1c76e638052aa", - "sha256:59e9237af027f8db85e5d78a9da2e328ae96f01d67a0d62abcecad3db7876908", - "sha256:60109741377367eef8ded9283a1bf629621b73acaf3e1e8aac9d1a0f50fa0f05", - "sha256:70558dd45c7a1f8046ba45792e489dd0f409bd8a3b7a0635ca9d3055223b3dff", - "sha256:81cb24e0f7bd9888596364e8d8ed0d65c2547c84884c67bb46d956faeed67396", - "sha256:833bebdc36bfeeedefc200ca9aee9b8eddd80f56b63ca1e886e18b97b1240edd", - "sha256:8a710eddb3e9e5f22bdbd458b5f211b94f59409ecd6896f15b9fee2cba266a59", - "sha256:9b492bb1a043540abb6e54fdb5537531e24962ca49c09f3b47dc4f9c37f6297c", - "sha256:a16db4f56699ef07f0249b953ff949aae641e50b2bdc4710f11c0d8d9089b296", - "sha256:a66cf99f08da65c501826a19e30f5a6e7ba942fdd79baba5ce2d51eebaa13444", - "sha256:b67a10799923f9fed546ca5f8b93a2819c71a60132d7a97b4a13fbdab66b278a", - "sha256:b7e0e6400c2f3ce78a9ae1cdd55b53166feedd003d60c033863881227129a4d3", - "sha256:c9dd6534c46ed782e2d7236767cd07115cb29ce8670c2fc0794f264de9024fe0", - "sha256:de13a8e378103af84a8bf6015ad1d2761d46f29b8393e8dd6d9bb7cb51bbb713", - "sha256:deafd70d04ab62428d4e291e8e2c0fb22f38690e6a9f23a67ee6c304087634da", - "sha256:df52e06a2754c2d905aad75a7dc06a732c804d9edbc87f06f47c8f483ba98bca" - ], - "index": "pypi", - "version": "==1.2.2" - }, - "gevent-websocket": { - "hashes": [ - "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242", - "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0" - ], - "index": "pypi", - "version": "==0.10.1" - }, - "greenlet": { - "hashes": [ - "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", - "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", - "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", - "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", - "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", - "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", - "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", - "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", - "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", - "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", - "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", - "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", - "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", - "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", - "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", - "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", - "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", - "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", - "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656" - ], - "version": "==0.4.15" - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "index": "pypi", - "version": "==19.9.0" - }, "identify": { "hashes": [ - "sha256:407cbb36e8b72b45cfa96a97ae13ccabca4c36557e03616958bd895dfcd3f77d", - "sha256:721abbbb1269fa1172799119981c22c5ace022544ce82eedc29b1b0d753baaa5" + "sha256:244e7864ef59f0c7c50c6db73f58564151d91345cd9b76ed793458953578cadd", + "sha256:8ff062f90ad4b09cfe79b5dfb7a12e40f19d2e68a5c9598a49be45f16aba7171" ], - "version": "==1.4.0" + "version": "==1.4.1" }, "importlib-metadata": { "hashes": [ @@ -343,59 +258,12 @@ "markers": "python_version < '3.7'", "version": "==1.0.2" }, - "itsdangerous": { - "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" - ], - "version": "==1.1.0" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "version": "==2.10" - }, "junit-xml": { "hashes": [ "sha256:602f1c480a19d64edb452bf7632f76b5f2cb92c1938c6e071dcda8ff9541dc21" ], "version": "==1.8" }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" - }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -405,11 +273,11 @@ }, "more-itertools": { "hashes": [ - "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", - "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" ], "markers": "python_version > '2.7'", - "version": "==6.0.0" + "version": "==7.0.0" }, "nodeenv": { "hashes": [ @@ -528,13 +396,6 @@ ], "version": "==16.4.3" }, - "werkzeug": { - "hashes": [ - "sha256:96da23fa8ccecbc3ae832a83df5c722c11547d021637faacb0bec4dd2f4666c8", - "sha256:ca5c2dcd367d6c0df87185b9082929d255358f5391923269335782b213d52655" - ], - "version": "==0.15.1" - }, "zipp": { "hashes": [ "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", @@ -7,21 +7,18 @@ Python sandbox runners for executing code in isolation aka snekbox The user sends a piece of python code to a snekbox, the snekbox executes the code and sends the result back to the users. ``` - +-------------+ +------------+ +-----------+ - input -> | |---------->| |-------->| | >----------+ - | WEBSERVER | | RABBITMQ | | SNEKBOX | execution | -result <- | |<----------| |<--------| | <----------+ - +-------------+ +------------+ +-----------+ - ^ ^ ^ - | | |- Executes python code - | | |- Returns result - | | +----------------------- - | | - | |- Message queues opens on demand and closes automatically - | +--------------------------------------------------------- + +-------------+ +-----------+ + input -> | |---------->| | >----------+ + | HTTP POST | | SNEKBOX | execution | +result <- | |<----------| | <----------+ + +-------------+ +-----------+ + ^ ^ + | |- Executes python code + | |- Returns result + | +----------------------- | - |- Uses websockets for asynchronous connection between webui and webserver - +------------------------------------------------------------------------- + |- HTTP POST Endpoint receives request and returns result + +--------------------------------------------------------- ``` @@ -36,6 +33,8 @@ result <- | |<----------| |<--------| | <------ | docker | 18.03.1-ce | | docker-compose | 1.21.2 | | nsjail | 2.5 | +| flask | 1.0.2 | +| gunicorn | 19.9 | _________________________________________ ## Setup local test @@ -83,38 +82,20 @@ python3.6 -ISq -c "print('test')" ## Development environment -Start a rabbitmq instance and get the container IP +Start the webserver with docker: ```bash -docker-compose up -d pdrmq -docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' rmq -# expected output with default setting: 172.17.0.2 -# If not, change the config.py file to match +docker-compose up -d ``` -rabbitmq webinterface: `http://localhost:15672` - -start the webserver - -```bash -docker-compose up -d pdsnkweb -netstat -plnt -# tcp 0.0.0.0:5000 LISTEN -``` - -`http://localhost:5000` - +Run locally with pipenv: ```bash pipenv run snekbox # for debugging -# or -docker-compose up pdsnk # for running the container ``` - +Visit: `http://localhost:8060` ________________________________________ ## Unit testing and lint -Make sure rabbitmq is running before running tests - ```bash pipenv run lint pipenv run test @@ -126,11 +107,6 @@ ________________________________________ ```bash # Build pipenv run buildbox -pipenv run buildweb - # Push pipenv run pushbox -pipenv run pushweb ``` - - diff --git a/config.py b/config.py deleted file mode 100644 index 5e4f648..0000000 --- a/config.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -import docker -from docker.errors import NotFound - - -def autodiscover(): - """Search for the snekbox container and return its IPv4 address.""" - - container_names = ["rmq", "pdrmq", "snekbox_pdrmq_1"] - - client = docker.from_env() - for name in container_names: - try: - container = client.containers.get(name) - if container.status == "running": - host = list(container.attrs.get('NetworkSettings').get('Networks').values()) - host = host[0]['IPAddress'] - return host - except NotFound: - continue - except Exception: - pass - - return '127.0.0.1' - - -USERNAME = os.environ.get('RMQ_USERNAME', 'guest') -PASSWORD = os.environ.get('RMQ_PASSWORD', 'guest') -HOST = os.environ.get('RMQ_HOST', autodiscover()) -PORT = 5672 -QUEUE = 'input' -EXCHANGE = QUEUE -ROUTING_KEY = QUEUE -EXCHANGE_TYPE = 'direct' diff --git a/docker-compose.yml b/docker-compose.yml index 3aedf14..2b22db4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,43 +1,7 @@ version: '3' services: - pdrmq: - hostname: "pdrmq" - image: pythondiscord/rmq:latest - expose: - - "15672" - ports: - - "15672:15672" - networks: - - sneknet - environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest - pdsnk: - privileged: true hostname: "pdsnk" + privileged: true image: pythondiscord/snekbox:latest - networks: - - sneknet - environment: - RMQ_HOST: pdrmq - RMQ_USERNAME: guest - RMQ_PASSWORD: guest - - pdsnkweb: - hostname: "pdsnkweb" - image: pythondiscord/snekboxweb:latest - networks: - - sneknet - ports: - - "5000:5000" - expose: - - "5000" - environment: - RMQ_HOST: pdrmq - RMQ_USERNAME: guest - RMQ_PASSWORD: guest - - -networks: - sneknet: + network_mode: "host" diff --git a/docker/Dockerfile b/docker/Dockerfile index e8fa8a5..b8d5637 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,7 @@ FROM pythondiscord/snekbox-base:latest +RUN apk add --update tini + RUN mkdir -p /snekbox COPY . /snekbox WORKDIR /snekbox @@ -7,4 +9,5 @@ WORKDIR /snekbox RUN pipenv --rm RUN pipenv sync +ENTRYPOINT ["/sbin/tini", "--"] CMD ["pipenv", "run", "snekbox"] diff --git a/docker/Dockerfile.webapp b/docker/Dockerfile.webapp deleted file mode 100644 index 988926d..0000000 --- a/docker/Dockerfile.webapp +++ /dev/null @@ -1,25 +0,0 @@ -FROM python:3.6.6-alpine3.7 - -RUN apk add --update tini -RUN apk add --update build-base - -ENV PIPENV_VENV_IN_PROJECT=1 -ENV PIPENV_IGNORE_VIRTUALENVS=1 -ENV PIPENV_NOSPIN=1 -ENV PIPENV_HIDE_EMOJIS=1 -ENV PYTHONPATH=/webapp - -RUN pip install pipenv - -RUN mkdir -p /webapp -COPY Pipfile /webapp -COPY Pipfile.lock /webapp -COPY . /webapp -WORKDIR /webapp - -RUN pipenv sync --dev - -EXPOSE 5000 - -ENTRYPOINT ["/sbin/tini", "--"] -CMD ["pipenv", "run", "snekweb"] @@ -1,113 +0,0 @@ -import time -import traceback - -import pika -from pika.exceptions import ConnectionClosed - -from config import EXCHANGE, EXCHANGE_TYPE, HOST, PASSWORD, PORT, QUEUE, ROUTING_KEY, USERNAME -from logs import log - - -class Rmq: - """Rabbit MQ (RMQ) implementation used for communication with the bot.""" - - def __init__(self): - self.credentials = pika.PlainCredentials(USERNAME, PASSWORD) - self.con_params = pika.ConnectionParameters(HOST, PORT, '/', self.credentials) - self.properties = pika.BasicProperties(content_type='text/plain', delivery_mode=1) - - def _declare(self, channel, queue): - channel.queue_declare( - queue=queue, - durable=False, # Do not commit messages to disk - arguments={'x-message-ttl': 5000}, # Delete message automatically after x milliseconds - auto_delete=True) # Delete queue when all connection are closed - - def consume(self, queue=QUEUE, callback=None, thread_ws=None, run_once=False): - """Subscribe to read from a RMQ channel.""" - - while True: - try: - connection = pika.BlockingConnection(self.con_params) - - try: - channel = connection.channel() - self._declare(channel, queue) - channel.basic_qos(prefetch_count=1) - - if not run_once: - channel.basic_consume( - lambda ch, method, properties, body: - callback(ch, method, properties, body, thread_ws=thread_ws), - queue=queue) - - log.info(f"Connected to host: {HOST} port: {PORT} queue: {queue}") - - if thread_ws: - if not thread_ws.closed: - thread_ws.send('{"service": "connected"}') - - if run_once: - return channel.basic_get(queue=queue) - - channel.start_consuming() - - except Exception: - exc = traceback.format_exc() - log.error(exc) - - finally: - connection.close() - - except ConnectionClosed: - if thread_ws: - if not thread_ws.closed: - log.error(f"Connection to {HOST} could not be established") - thread_ws.send('{"service": "disconnected"}') - exit(1) - - log.error(f"Connection lost, reconnecting to {HOST}") - - time.sleep(2) - - def publish(self, message, queue=QUEUE, routingkey=ROUTING_KEY, exchange=EXCHANGE): - """Open a connection to publish (write) to a RMQ channel.""" - - try: - connection = pika.BlockingConnection(self.con_params) - - try: - channel = connection.channel() - - self._declare(channel, queue) - - channel.exchange_declare( - exchange=exchange, - exchange_type=EXCHANGE_TYPE) - - channel.queue_bind( - exchange=exchange, - queue=queue, - routing_key=routingkey) - - result = channel.basic_publish( - exchange=exchange, - routing_key=routingkey, - body=message, - properties=self.properties) - - if result: - return result - - else: - log.error(f"Message '{message}' not delivered") - - except ConnectionClosed: - log.error(f"Could not send message, connection to {HOST} was lost") - exit(1) - - finally: - connection.close() - - except ConnectionClosed: - log.error(f"Could not connect to {HOST}") @@ -1,10 +1,8 @@ -import json -import multiprocessing import os import subprocess import sys -from rmq import Rmq +from flask import Flask, jsonify, render_template, request class Snekbox: @@ -98,46 +96,38 @@ class Snekbox: return output - def execute(self, body): - """ - Handles execution of a raw JSON-formatted RMQ message, contained in ``body``. - The message metadata, including the Python code to be executed, is - extracted from the message body. The code is then executed in the - isolated environment, and the results of the execution published - to RMQ. Once published, the system exits, since the snekboxes - are created and disposed of per-execution. - """ +snekbox = Snekbox() + +# Load app +app = Flask(__name__) +app.use_reloader = False + +# Logging +log = app.logger + - msg = body.decode('utf-8') - result = '' - snek_msg = json.loads(msg) - snekid = snek_msg['snekid'] - snekcode = snek_msg['message'].strip() [email protected]('/') +def index(): + """Return a page with a form for inputting code to be executed.""" - result = self.python3(snekcode) + return render_template('index.html') - rmq.publish(result, - queue=snekid, - routingkey=snekid, - exchange=snekid) - exit(0) - def message_handler(self, ch, method, properties, body, thread_ws=None): - """Spawns a daemon process that handles RMQ messages.""" [email protected]('/result', methods=["POST", "GET"]) +def result(): + """Execute code and return a page displaying the results.""" - p = multiprocessing.Process(target=self.execute, args=(body,)) - p.daemon = True - p.start() + if request.method == "POST": + code = request.form["Code"] + output = snekbox.python3(code) + return render_template('result.html', code=code, result=output) - ch.basic_ack(delivery_tag=method.delivery_tag) [email protected]('/input', methods=["POST"]) +def code_input(): + """Execute code and return the results.""" -if __name__ == '__main__': - try: - rmq = Rmq() - snkbx = Snekbox() - rmq.consume(callback=snkbx.message_handler) - except KeyboardInterrupt: - print('Exited') - exit(0) + body = request.get_json() + output = snekbox.python3(body["code"]) + return jsonify(input=body["code"], output=output) diff --git a/snekweb.py b/snekweb.py deleted file mode 100644 index ff1a72c..0000000 --- a/snekweb.py +++ /dev/null @@ -1,74 +0,0 @@ -import json -import logging -import threading -import traceback - -from flask import Flask, render_template -from flask_sockets import Sockets -from rmq import Rmq - -# Load app -app = Flask(__name__) -app.jinja_env.auto_reload = True -sockets = Sockets(app) - -# Logging -gunicorn_logger = logging.getLogger('gunicorn.error') -app.logger.handlers = gunicorn_logger.handlers -app.logger.setLevel(gunicorn_logger.level) -log = app.logger - - [email protected]('/') -def index(): - """Root path returns standard index.html.""" - - return render_template('index.html') - - [email protected]('/ws/<snekboxid>') -def websocket_route(ws, snekboxid): - """Opens a websocket that spawns and connects to a snekbox daemon.""" - - localdata = threading.local() - localdata.thread_ws = ws - - rmq = Rmq() - - def message_handler(ch, method, properties, body, thread_ws): - msg = body.decode('utf-8') - thread_ws.send(msg) - ch.basic_ack(delivery_tag=method.delivery_tag) - - consumer_parameters = {'queue': snekboxid, - 'callback': message_handler, - 'thread_ws': localdata.thread_ws} - - consumer = threading.Thread( - target=rmq.consume, - kwargs=consumer_parameters) - - consumer.daemon = True - consumer.start() - - try: - while not ws.closed: - message = ws.receive() - if message: - snek_msg = json.dumps({"snekid": snekboxid, "message": message}) - log.info(f"User {snekboxid} sends message\n{message.strip()}") - rmq.publish(snek_msg) - - except Exception: - log.info(traceback.format_exc()) - - finally: - if not ws.closed: - ws.close() - - -if __name__ == '__main__': - from gevent import pywsgi - from geventwebsocket.handler import WebSocketHandler - server = pywsgi.WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler) - server.serve_forever() diff --git a/templates/index.html b/templates/index.html index 8de9627..41980d1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,106 +1,14 @@ <!DOCTYPE html> -<meta charset="utf-8" /> -<title>snekboxweb</title> -<script language="javascript" type="text/javascript"> - - -let _ready = false -let snekbox_id -var output; - -snekbox_id = sessionStorage.getItem("snekbox_id"); -console.log(snekbox_id) -if (snekbox_id == null) { - snekbox_id = generate_id() - sessionStorage.setItem("snekbox_id", snekbox_id) - console.log(snekbox_id) -} - -function init(){ - output = document.getElementById("output"); - websocketHandler(); -} - -function websocketHandler(){ - var here = window.location.host; - var wsUri = `ws://${here}/ws/`+snekbox_id; - websocket = new WebSocket(wsUri); - websocket.onopen = function(evt) { onOpen(evt) }; - websocket.onclose = function(evt) { onClose(evt) }; - websocket.onmessage = function(evt) { onMessage(evt) }; - websocket.onerror = function(evt) { onError(evt) }; -} - -function onOpen(evt){ - _ready = true - console.log("CONNECTED"); -} - -function onClose(evt){ - _ready = false - console.log("DISCONNECTED"); -} - -function onMessage(evt){ - writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>'); -} - -function exit(){ - websocket.close(); -} - -function onError(evt){ - _ready = false - writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data); -} - -function sendMessage(msg){ - waitForSocketConnection(function(){ - websocket.send(msg); - }); - console.log("sent message "+msg) -} - -function waitForSocketConnection(callback){ - setTimeout( - function () { - if (_ready === true) { - if(callback != null){ - callback();} - return; - } - else { - waitForSocketConnection(callback);} - - }, 500); // milliseconds -} - -function writeToScreen(message){ - var pre = document.createElement("p"); - pre.style.wordWrap = "break-word"; - pre.innerHTML = message; - output.appendChild(pre); -} - -function sendFromInput(){ - var msg = document.getElementById("field1").value; - sendMessage(msg) -} - -function generate_id(){ - return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); -} - -window.addEventListener("load", init, false); - -</script> - -<textarea rows="4" cols="50" type="text" id="field1"> +<html> + <meta charset="utf-8" /> + <title>snekboxweb</title> + <body> + <form action="/result" method="POST"> + <p>Code:<br><textarea name="Code" rows="20" cols="80"> def sum(a,b): return a+b -print( sum(1,2) ) -</textarea> -<br> -<button onclick="sendFromInput()">Send</button> -<button onclick="exit()">disconnect from websocket</button> -<div id="output"></div> +print( sum(1,2) )</textarea></p> + <p><input type="submit" value="Run"></p> + </form> + </body> +</html> diff --git a/templates/result.html b/templates/result.html new file mode 100644 index 0000000..e339605 --- /dev/null +++ b/templates/result.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <meta charset="utf-8" /> + <title>snekboxweb</title> + <body> + <p>Code Evaluated:<br><pre>{{ code }}</pre></p> + <p>Results:<br><pre>{{ result }}</pre></p> + </body> +</html> diff --git a/tests/test_snekbox.py b/tests/test_snekbox.py index e2505d6..cc79a2a 100644 --- a/tests/test_snekbox.py +++ b/tests/test_snekbox.py @@ -1,12 +1,6 @@ import unittest -import pytest -import os -import json from snekbox import Snekbox -from rmq import Rmq - -r = Rmq() snek = Snekbox() @@ -24,12 +18,14 @@ class SnekTests(unittest.TestCase): # self.assertEquals(result.strip(), 'timed out or memory limit exceeded') def test_timeout(self): - code = ('x = "*"\n' - 'while True:\n' - ' try:\n' - ' x = x * 99\n' - ' except:\n' - ' continue\n') + code = ( + 'x = "*"\n' + 'while True:\n' + ' try:\n' + ' x = x * 99\n' + ' except:\n' + ' continue\n' + ) result = snek.python3(code) self.assertEquals(result.strip(), 'timed out or memory limit exceeded') |