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') | 
