aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Pipfile18
-rw-r--r--Pipfile.lock267
-rw-r--r--README.md58
-rw-r--r--config.py35
-rw-r--r--docker-compose.yml40
-rw-r--r--docker/Dockerfile3
-rw-r--r--docker/Dockerfile.webapp25
-rw-r--r--rmq.py113
-rw-r--r--snekbox.py64
-rw-r--r--snekweb.py74
-rw-r--r--templates/index.html114
-rw-r--r--templates/result.html9
-rw-r--r--tests/test_snekbox.py20
13 files changed, 146 insertions, 694 deletions
diff --git a/Pipfile b/Pipfile
index 3e0174d..221263d 100644
--- a/Pipfile
+++ b/Pipfile
@@ -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",
diff --git a/README.md b/README.md
index 552140f..1f7c4aa 100644
--- a/README.md
+++ b/README.md
@@ -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"]
diff --git a/rmq.py b/rmq.py
deleted file mode 100644
index 919ef19..0000000
--- a/rmq.py
+++ /dev/null
@@ -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}")
diff --git a/snekbox.py b/snekbox.py
index f8d7c31..65fc4b3 100644
--- a/snekbox.py
+++ b/snekbox.py
@@ -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()
+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
-
-
-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')