├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── api_docs ├── README.md ├── admin │ ├── README.md │ ├── create_language.md │ ├── create_user.md │ ├── delete_language.md │ ├── delete_user.md │ ├── get_language.md │ ├── get_user.md │ ├── list_languages.md │ ├── list_users.md │ └── update_user.md ├── list_languages.md ├── list_versions.md └── run.md ├── apps └── glot │ └── src │ ├── config.erl │ ├── docker │ ├── docker.erl │ ├── docker_attach.erl │ ├── docker_attach_sup.erl │ └── docker_url.erl │ ├── glot.app.src │ ├── glot.erl │ ├── glot_app.erl │ ├── glot_sup.erl │ ├── http │ ├── http_auth.erl │ └── http_util.erl │ ├── logging │ ├── event_log_srv.erl │ ├── http_log_srv.erl │ └── log.erl │ ├── models │ ├── admin_token.erl │ ├── language.erl │ ├── language_run.erl │ └── users.erl │ ├── persistent │ ├── language_srv.erl │ ├── persist.erl │ └── user_srv.erl │ ├── resources │ ├── admin_language_resource.erl │ ├── admin_languages_resource.erl │ ├── admin_root_resource.erl │ ├── admin_user_resource.erl │ ├── admin_users_resource.erl │ ├── images_resource.erl │ ├── language_resource.erl │ ├── language_run_resource.erl │ ├── languages_resource.erl │ └── root_resource.erl │ └── util.erl ├── config ├── relx.config └── vm.args ├── devel.sh ├── dialyzer.sh ├── docker-pull.sh ├── docker_server_config.md ├── make_release.sh ├── make_release_docker.sh ├── rebar.config └── set_version.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [prasmussen] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | apps/*/ebin 2 | apps/*/.rebar 3 | deps/* 4 | rel/glot 5 | log 6 | .rebar/* 7 | *.dump 8 | _rel 9 | 10 | [._]*.s[a-w][a-z] 11 | [._]s[a-w][a-z] 12 | *.un~ 13 | Session.vim 14 | .netrwhist 15 | *~ 16 | ._* 17 | env_prod.sh 18 | env_dev.sh 19 | scripts/run.glot.io 20 | _rel_start.sh 21 | scripts 22 | rebar.lock 23 | _build 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: bash 3 | services: docker 4 | 5 | script: 6 | - docker build --no-cache -t glot/run:travisci . 7 | 8 | after_script: 9 | - docker images 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM erlang:22-alpine 2 | 3 | COPY . /glot-run/ 4 | 5 | RUN export DEPS="\ 6 | git \ 7 | " &&\ 8 | apk --update add $DEPS &&\ 9 | rm -rf /var/cache/apk/* &&\ 10 | git clone https://github.com/erlang/rebar3.git /rebar3 &&\ 11 | cd /rebar3 &&\ 12 | escript bootstrap &&\ 13 | cd /glot-run &&\ 14 | /rebar3/rebar3 compile &&\ 15 | /rebar3/rebar3 release -c config/relx.config &&\ 16 | mv /glot-run/_build/default/rel/glot /glot &&\ 17 | cd / &&\ 18 | rm -rf /rebar3 &&\ 19 | rm -rf /glot-run &&\ 20 | apk del $DEPS &&\ 21 | apk --update add ncurses-libs &&\ 22 | rm -rf /var/cache/apk/* 23 | 24 | CMD ["/glot/bin/glot", "foreground"] 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Petter Rasmussen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | glot-run 2 | ======== 3 | 4 | ## THIS REPO IS DEPRECATED AND HAS BEEN REPLACED BY [docker-run](https://github.com/glotcode/docker-run) and [glot-run 2](https://github.com/glotcode/glot-run) 5 | 6 | 7 | ## Overview 8 | glot-run provides a http api for running code inside docker containers. 9 | The api is described [here](https://github.com/prasmussen/glot-run/tree/master/api_docs). 10 | 11 | ## Run 12 | The download above is a standard erlang release that includes a start script. 13 | To start glot-run in the foreground type: `glot/bin/glot foreground`. 14 | 15 | ## Environment variables 16 | glot-run takes it's configuration from environment variables. 17 | All vars needs to be set, no default values are provided. 18 | 19 | | Variable name | Allowed values | Example | Description | 20 | |:---------------------|:------------------------------|:----------------------|:--------------------------------------------------------------| 21 | | API_ENVIRONMENT | development | production | production | Development mode will enable auto compiling of changed files | 22 | | API_HTTP_LISTEN_IP | <ipv4 address> | 0.0.0.0 | Listen ip | 23 | | API_HTTP_LISTEN_PORT | 1-65535 | 8090 | Listen port | 24 | | DATA_PATH | <filepath> | /home/app/data/ | Path to save data files (users, languages) | 25 | | LOG_PATH | <filepath> | /home/app/log/ | Path to save logs | 26 | | BASE_URL | <url> | https://run.glot.io | Base url to where the api is hosted | 27 | | ADMIN_TOKEN | <string> | some-secret | Admin token used to access the /admin endpoints | 28 | | DOCKER_API_URL | <url> | http://10.0.0.2:2375 | Url to docker api (see docker configuration section below) | 29 | | DOCKER_RUN_TIMEOUT | <seconds> | 15 | Maximum number of seconds a container is allowed to run | 30 | | MAX_OUTPUT_SIZE | <bytes> | 100000 | Maximum number of bytes allowed from the output of a run | 31 | 32 | ## Api users 33 | An api token is required to run code. Users can be created with the `/admin/users` endpoint. 34 | See the [api docs](https://github.com/prasmussen/glot-run/tree/master/api_docs/admin) for more details. 35 | 36 | ## Languages 37 | Languages can be added with the `/admin/languages` endpoint. A language has 38 | a name, version and the name of a docker image that will be used when running 39 | code for the given language/version. 40 | See the [api docs](https://github.com/prasmussen/glot-run/tree/master/api_docs/admin) for more details. 41 | 42 | ## Docker images 43 | When a run request is posted to glot-run it will create a new temporary container from 44 | the image that handles the given language/version. The container is required 45 | to listen for a json payload on stdin and must write the run result to stdout 46 | as a json object containing the properties: stdout, stderr and error. 47 | An application that does this is [glot-code-runner](https://github.com/prasmussen/glot-code-runner). 48 | Example images can be found [here](https://github.com/prasmussen/glot-containers). 49 | 50 | ### Container payload 51 | The payload `{"files": [{"name": "main.py", "content": "print(42)"}]}` posted to 52 | `/languages/python/latest` will result in this payload being sent to the 53 | container: `{"language": "python", "files": [{"name": "main.py", "content": "print(42)"}]}`. 54 | A successful run should yield this response from the container: `{"stdout": "42\n", "stderr": "", "error": ""}`. 55 | 56 | ## Docker configuration 57 | See [docker_server_config.md](docker_server_config.md) 58 | -------------------------------------------------------------------------------- /api_docs/README.md: -------------------------------------------------------------------------------- 1 | ## Run API 2 | 3 | ### Overview 4 | | Action | Method | Route | Requires token | 5 | |:------------------------------------|:-------|:------------------------------|:---------------| 6 | | [List languages](list_languages.md) | GET | /languages | No | 7 | | [List versions](list_versions.md) | GET | /languages/:language | No | 8 | | [Run code](run.md) | POST | /languages/:language/:version | Yes | -------------------------------------------------------------------------------- /api_docs/admin/README.md: -------------------------------------------------------------------------------- 1 | ## Run admin API 2 | 3 | ### Overview 4 | | Action | Method | Route | Requires admin token | 5 | |:-----------------------------------------------|:-------|:---------------------|:---------------------| 6 | | [List users](list_users.md) | GET | /admin/users | Yes | 7 | | [Create user](create_user.md) | POST | /admin/users | Yes | 8 | | [Get user](get_user.md) | GET | /admin/users/:id | Yes | 9 | | [Update user](update_user.md) | PUT | /admin/users/:id | Yes | 10 | | [Delete user](delete_user.md) | DELETE | /admin/users/:id | Yes | 11 | | [List languages](list_languages.md) | GET | /admin/languages | Yes | 12 | | [Create / update language](create_language.md) | PUT | /admin/languages | Yes | 13 | | [Get language](get_language.md) | GET | /admin/languages/:id | Yes | 14 | | [Delete language](delete_language.md) | DELETE | /admin/languages/:id | Yes | 15 | -------------------------------------------------------------------------------- /api_docs/admin/create_language.md: -------------------------------------------------------------------------------- 1 | ## Create language 2 | 3 | ### Create language request 4 | curl --request PUT \ 5 | --header 'Authorization: Token some-secret' \ 6 | --header 'Content-type: application/json' \ 7 | --data '{"name": "erlang", "version": "latest", "image": "glot/erlang:latest"}' \ 8 | --url 'https://run.glot.io/admin/languages' 9 | 10 | 11 | ### Example response data 12 | { 13 | "id": "eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4", 14 | "image": "glot/erlang:latest", 15 | "name": "erlang", 16 | "url": "https://run.glot.io/admin/languages/eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4", 17 | "version": "latest" 18 | } 19 | -------------------------------------------------------------------------------- /api_docs/admin/create_user.md: -------------------------------------------------------------------------------- 1 | ## Create user 2 | 3 | ### Create user request 4 | curl --request POST \ 5 | --header 'Authorization: Token some-secret' \ 6 | --header 'Content-type: application/json' \ 7 | --data '{"token": "d11088bc-a29d-4d49-a633-b1b1ae807064"}' \ 8 | --url 'https://run.glot.io/admin/users' 9 | 10 | 11 | ### Example response data 12 | { 13 | "created": "2015-06-28T18:40:52Z", 14 | "id": "8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c", 15 | "modified": "2015-06-28T18:40:52Z", 16 | "token": "d11088bc-a29d-4d49-a633-b1b1ae807064", 17 | "url": "https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c" 18 | } 19 | -------------------------------------------------------------------------------- /api_docs/admin/delete_language.md: -------------------------------------------------------------------------------- 1 | ## Delete language 2 | 3 | ### Delete language request 4 | curl --request DELETE \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/languages/eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4' 7 | 8 | #### Response 9 | A successful delete returns a 204 No Content, with an empty body. 10 | -------------------------------------------------------------------------------- /api_docs/admin/delete_user.md: -------------------------------------------------------------------------------- 1 | ## Delete user 2 | 3 | ### Delete user request 4 | curl --request DELETE \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c' 7 | 8 | #### Response 9 | A successful delete returns a 204 No Content, with an empty body. 10 | -------------------------------------------------------------------------------- /api_docs/admin/get_language.md: -------------------------------------------------------------------------------- 1 | ## Get language 2 | 3 | ### Get language request 4 | curl --request GET \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/languages/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c' 7 | 8 | 9 | ### Example response data 10 | { 11 | "id": "eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4", 12 | "image": "glot/erlang:latest", 13 | "name": "erlang", 14 | "version": "latest" 15 | } 16 | -------------------------------------------------------------------------------- /api_docs/admin/get_user.md: -------------------------------------------------------------------------------- 1 | ## Get user 2 | 3 | ### Get user request 4 | curl --request GET \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c' 7 | 8 | 9 | ### Example response data 10 | { 11 | "created": "2015-06-28T18:40:52Z", 12 | "id": "8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c", 13 | "modified": "2015-06-28T18:40:52Z", 14 | "token": "d11088bc-a29d-4d49-a633-b1b1ae807064", 15 | "url": "https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c" 16 | } 17 | -------------------------------------------------------------------------------- /api_docs/admin/list_languages.md: -------------------------------------------------------------------------------- 1 | ## Listing languages 2 | 3 | ### List languages request 4 | curl --request GET \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/languages' 7 | 8 | 9 | ### Example response data 10 | [ 11 | { 12 | "id": "eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4", 13 | "image": "glot/erlang:latest", 14 | "name": "erlang", 15 | "url": "https://run.glot.io/admin/languages/eeecd07765ce8cb4a60e52177f8e36bf80d5dbd4", 16 | "version": "latest" 17 | }, 18 | { 19 | "id": "c82a38621af719efa49dc0d7b88c6467cd45dc77", 20 | "image": "glot/haskell:latest", 21 | "name": "haskell", 22 | "url": "https://run.glot.io/admin/languages/c82a38621af719efa49dc0d7b88c6467cd45dc77", 23 | "version": "latest" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /api_docs/admin/list_users.md: -------------------------------------------------------------------------------- 1 | ## Listing users 2 | 3 | ### List users request 4 | curl --request GET \ 5 | --header 'Authorization: Token some-secret' \ 6 | --url 'https://run.glot.io/admin/users' 7 | 8 | 9 | ### Example response data 10 | [ 11 | { 12 | "created": "2015-04-19T22:22:09Z", 13 | "id": "5b48c546-8e30-4ca6-ade0-e7e8765e71bc", 14 | "modified": "2015-04-19T22:22:09Z", 15 | "token": "ffb05492-258d-4dd6-9713-eee722874ff6", 16 | "url": "https://run.glot.io/admin/users/5b48c546-8e30-4ca6-ade0-e7e8765e71bc" 17 | }, 18 | { 19 | "created": "2015-04-19T22:00:04Z", 20 | "id": "6165b155-6133-4cf5-b8b5-0f675f221257", 21 | "modified": "2015-04-19T22:00:04Z", 22 | "token": "d1c31bd3-9afe-4ee6-8ea6-5db9ff1375ef", 23 | "url": "https://run.glot.io/admin/users/6165b155-6133-4cf5-b8b5-0f675f221257" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /api_docs/admin/update_user.md: -------------------------------------------------------------------------------- 1 | ## Update user 2 | 3 | ### Update user token request 4 | curl --request PUT \ 5 | --header 'Authorization: Token some-secret' \ 6 | --header 'Content-type: application/json' \ 7 | --data '{"token": "62c8e937-780f-4ca8-ba93-168057263afe"}' \ 8 | --url 'https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c' 9 | 10 | 11 | ### Example response data 12 | { 13 | "created": "2015-06-28T18:40:52Z", 14 | "id": "8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c", 15 | "modified": "2015-06-28T18:40:52Z", 16 | "token": "62c8e937-780f-4ca8-ba93-168057263afe", 17 | "url": "https://run.glot.io/admin/users/8aa7e7fc-4d9b-4f97-a950-887b55ddcf1c" 18 | } 19 | -------------------------------------------------------------------------------- /api_docs/list_languages.md: -------------------------------------------------------------------------------- 1 | ## Listing languages 2 | 3 | ##### List languages 4 | curl --request GET \ 5 | --url 'https://run.glot.io/languages' 6 | 7 | ### Example response 8 | [ 9 | { 10 | "name": "assembly", 11 | "url": "https://run.glot.io/languages/assembly" 12 | }, 13 | { 14 | "name": "bash", 15 | "url": "https://run.glot.io/languages/bash" 16 | }, 17 | { 18 | "name": "c", 19 | "url": "https://run.glot.io/languages/c" 20 | }, 21 | { 22 | "name": "clojure", 23 | "url": "https://run.glot.io/languages/clojure" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /api_docs/list_versions.md: -------------------------------------------------------------------------------- 1 | ## Listing versions 2 | 3 | ##### List versions 4 | curl --request GET \ 5 | --url 'https://run.glot.io/languages/python' 6 | 7 | ### Example response 8 | [ 9 | { 10 | "url": "https://run.glot.io/languages/python/2", 11 | "version": "2" 12 | }, 13 | { 14 | "url": "https://run.glot.io/languages/python/latest", 15 | "version": "latest" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /api_docs/run.md: -------------------------------------------------------------------------------- 1 | ## Run code 2 | 3 | ##### Run code 4 | ```bash 5 | curl --request POST \ 6 | --header 'Authorization: Token 0123456-789a-bcde-f012-3456789abcde' \ 7 | --header 'Content-type: application/json' \ 8 | --data '{"files": [{"name": "main.py", "content": "print(42)"}]}' \ 9 | --url 'https://run.glot.io/languages/python/latest' 10 | ``` 11 | 12 | 13 | ## Example data 14 | 15 | ### Simple example 16 | ##### Request 17 | ```javascript 18 | { 19 | "files": [ 20 | { 21 | "name": "main.py", 22 | "content": "print(42)" 23 | } 24 | ] 25 | } 26 | ``` 27 | 28 | ##### Response 29 | ```javascript 30 | { 31 | "stdout": "42\n", 32 | "stderr": "", 33 | "error": "" 34 | } 35 | ``` 36 | 37 | ### Read from stdin 38 | ##### Request 39 | ```javascript 40 | { 41 | "stdin": "42", 42 | "files": [ 43 | { 44 | "name": "main.py", 45 | "content": "print(input('Number from stdin: '))" 46 | } 47 | ] 48 | } 49 | ``` 50 | 51 | ##### Response 52 | ```javascript 53 | { 54 | "stdout": "Number from stdin: 42\n", 55 | "stderr": "", 56 | "error": "" 57 | } 58 | ``` 59 | 60 | ### Custom run command 61 | ##### Request 62 | ```javascript 63 | { 64 | "command": "bash main.sh 42", 65 | "files": [ 66 | { 67 | "name": "main.sh", 68 | "content": "echo Number from arg: $1" 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | ##### Response 75 | ```javascript 76 | { 77 | "stdout": "Number from arg: 42\n", 78 | "stderr": "", 79 | "error": "" 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /apps/glot/src/config.erl: -------------------------------------------------------------------------------- 1 | -module(config). 2 | 3 | -export([ 4 | environment/0, 5 | glot_version/0, 6 | glot_description/0, 7 | http_listen_ip/0, 8 | http_listen_port/0, 9 | languages_data_path/0, 10 | users_data_path/0, 11 | http_log_path/0, 12 | event_log_path/0, 13 | base_url/0, 14 | admin_token/0, 15 | max_output_size/0, 16 | docker_api_url/0, 17 | docker_run_timeout/0, 18 | docker_container_config/1 19 | ]). 20 | 21 | environment() -> 22 | list_to_atom(os:getenv("API_ENVIRONMENT")). 23 | 24 | glot_version() -> 25 | {ok, Vsn} = application:get_key(glot, vsn), 26 | list_to_binary(Vsn). 27 | 28 | glot_description() -> 29 | {ok, Desc} = application:get_key(glot, description), 30 | list_to_binary(Desc). 31 | 32 | http_listen_ip() -> 33 | {ok, Addr} = inet:parse_address(os:getenv("API_HTTP_LISTEN_IP")), 34 | Addr. 35 | 36 | http_listen_port() -> 37 | list_to_integer(os:getenv("API_HTTP_LISTEN_PORT")). 38 | 39 | data_path() -> 40 | Path = os:getenv("DATA_PATH"), 41 | filelib:ensure_dir(Path), 42 | Path. 43 | 44 | log_path() -> 45 | Path = os:getenv("LOG_PATH"), 46 | filelib:ensure_dir(Path), 47 | Path. 48 | 49 | languages_data_path() -> 50 | filename:join(data_path(), "languages.data"). 51 | 52 | users_data_path() -> 53 | filename:join(data_path(), "users.data"). 54 | 55 | http_log_path() -> 56 | filename:join(log_path(), "http.log"). 57 | 58 | event_log_path() -> 59 | filename:join(log_path(), "event.log"). 60 | 61 | base_url() -> 62 | list_to_binary(os:getenv("BASE_URL")). 63 | 64 | admin_token() -> 65 | list_to_binary(os:getenv("ADMIN_TOKEN")). 66 | 67 | max_output_size() -> 68 | list_to_integer(os:getenv("MAX_OUTPUT_SIZE")). 69 | 70 | docker_api_url() -> 71 | list_to_binary(os:getenv("DOCKER_API_URL")). 72 | 73 | docker_run_timeout() -> 74 | list_to_integer(os:getenv("DOCKER_RUN_TIMEOUT")). 75 | 76 | docker_container_config(Image) -> 77 | Config = default_docker_config(), 78 | Config#{<<"Image">> => Image}. 79 | 80 | default_docker_config() -> 81 | #{ 82 | <<"Hostname">> => <<"glot-runner">>, 83 | <<"Domainname">> => <<"">>, 84 | <<"User">> => <<"glot">>, 85 | <<"AttachStdin">> => true, 86 | <<"AttachStdout">> => true, 87 | <<"AttachStderr">> => true, 88 | <<"Tty">> => false, 89 | <<"OpenStdin">> => true, 90 | <<"StdinOnce">> => true, 91 | <<"Env">> => null, 92 | <<"Cmd">> => [<<"/home/glot/runner">>], 93 | <<"Entrypoint">> => <<"/home/glot/runner">>, 94 | <<"Image">> => <<"">>, 95 | <<"Volumes">> => #{}, 96 | <<"WorkingDir">> => <<"">>, 97 | <<"NetworkDisabled">> => true, 98 | %<<"MacAddress">> => <<"12:34:56:78:9a:bc">>, 99 | <<"Memory">> => 500000000, 100 | %<<"MemorySwap">> => 0, 101 | %<<"CpuShares">> => 512, 102 | %<<"Cpuset">> => <<"0">>, 103 | <<"ExposedPorts">> => #{}, 104 | %<<"SecurityOpt">> => null, 105 | <<"HostConfig">> => #{ 106 | <<"Binds">> => [], 107 | <<"Links">> => [], 108 | <<"LxcConf">> => #{ 109 | <<"lxc.utsname">> => <<"docker">> 110 | }, 111 | <<"PortBindings">> => #{}, 112 | <<"PublishAllPorts">> => false, 113 | <<"Privileged">> => false, 114 | <<"Dns">> => [<<"8.8.8.8">>], 115 | %<<"DnsSearch">> => [<<"">>], 116 | <<"ExtraHosts">> => null, 117 | <<"VolumesFrom">> => [], 118 | <<"CapAdd">> => [<<"NET_ADMIN">>], 119 | <<"CapDrop">> => [<<"MKNOD">>], 120 | <<"RestartPolicy">> => #{ 121 | <<"Name">> => <<"">>, 122 | <<"MaximumRetryCount">> => 0 123 | }, 124 | <<"NetworkMode">> => <<"bridge">>, 125 | <<"Devices">> => [], 126 | <<"Ulimits">> => [ 127 | #{<<"Name">> => <<"nofile">>, <<"Soft">> => 90, <<"Hard">> => 100}, 128 | #{<<"Name">> => <<"nproc">>, <<"Soft">> => 90, <<"Hard">> => 100} 129 | ] 130 | } 131 | }. 132 | -------------------------------------------------------------------------------- /apps/glot/src/docker/docker.erl: -------------------------------------------------------------------------------- 1 | -module(docker). 2 | 3 | -export([ 4 | container_create/1, 5 | container_start/1, 6 | container_remove/1, 7 | container_attach/1, 8 | container_detach/2, 9 | container_send/2 10 | ]). 11 | 12 | container_create(Configuration) -> 13 | {ok, 201, _, Client} = hackney:post( 14 | docker_url:container_create(config:docker_api_url()), 15 | [{<<"Content-Type">>, <<"application/json">>}], 16 | jsx:encode(Configuration), 17 | [{pool, default}] 18 | ), 19 | {ok, Data} = hackney:body(Client), 20 | proplists:get_value(<<"Id">>, jsx:decode(Data)). 21 | 22 | container_start(Id) -> 23 | {ok, 204, _, Client} = hackney:post( 24 | docker_url:container_start(config:docker_api_url(), Id), 25 | [{<<"Content-Type">>, <<"application/json">>}], 26 | <<"">>, 27 | [{pool, default}] 28 | ), 29 | hackney:skip_body(Client), 30 | ok. 31 | 32 | container_remove(Id) -> 33 | Url = docker_url:container_remove(config:docker_api_url(), Id, [ 34 | {v, true}, {force, true} 35 | ]), 36 | {ok, 204, _, Client} = hackney:delete( 37 | Url, 38 | [{<<"Content-Type">>, <<"application/json">>}], 39 | <<"">>, 40 | [{pool, default}] 41 | ), 42 | hackney:skip_body(Client), 43 | ok. 44 | 45 | container_attach(Id) -> 46 | {ok, Pid} = docker_attach_sup:start_child(), 47 | docker_attach:attach(Pid, Id), 48 | Pid. 49 | 50 | container_detach(Pid, Reason) -> 51 | docker_attach:detach(Pid, Reason). 52 | 53 | container_send(Pid, Payload) -> 54 | docker_attach:send(Pid, Payload). 55 | -------------------------------------------------------------------------------- /apps/glot/src/docker/docker_attach.erl: -------------------------------------------------------------------------------- 1 | -module(docker_attach). 2 | -behaviour(gen_fsm). 3 | 4 | -include_lib("hackney/include/hackney_lib.hrl"). 5 | 6 | % Wait 1 hour before giving up when using sync calls 7 | -define(SYNC_TIMEOUT, 3600000). 8 | 9 | -export([ 10 | start_link/0, 11 | init/1, 12 | handle_event/3, 13 | handle_sync_event/4, 14 | handle_info/3, 15 | code_change/4, 16 | terminate/3, 17 | 18 | ready/3, 19 | recv_http/2, 20 | recv_http_header/2, 21 | attached/3, 22 | recv_response/2, 23 | 24 | attach/2, 25 | detach/2, 26 | send/2 27 | ]). 28 | 29 | -record(state, { 30 | socket, 31 | callback_pid, 32 | buffer=[], 33 | buffer_size=0 34 | }). 35 | 36 | start_link() -> 37 | gen_fsm:start_link(?MODULE, [], []). 38 | 39 | init([]) -> 40 | log:event(<<"Transition to ready state">>), 41 | {ok, ready, #state{}}. 42 | 43 | ready({attach, ContainerId}, From, State) -> 44 | ApiUrl = config:docker_api_url(), 45 | #hackney_url{host=Host, port=Port} = hackney_url:parse_url(ApiUrl), 46 | log:event([<<"Connect to ">>, list_to_binary(Host), <<":">>, integer_to_binary(Port)]), 47 | {ok, Socket} = gen_tcp:connect(Host, Port, [ 48 | binary, {active, true}, {packet, line}, {keepalive, true} 49 | ]), 50 | gen_tcp:send(Socket, raw_container_attach_request(ContainerId)), 51 | NewState = State#state{socket=Socket, callback_pid=From}, 52 | log:event(<<"Transition to recv_http state">>), 53 | {next_state, recv_http, NewState}. 54 | 55 | recv_http(<<"HTTP/1.1 101 UPGRADED\r\n">>, State) -> 56 | log:event(<<"Transition to recv_http_header state">>), 57 | {next_state, recv_http_header, State}. 58 | 59 | recv_http_header(<<"\r\n">>, State=#state{callback_pid=From}) -> 60 | gen_fsm:reply(From, ok), 61 | log:event(<<"Transition to attached state">>), 62 | {next_state, attached, State}; 63 | recv_http_header(_Header, State) -> 64 | {next_state, recv_http_header, State}. 65 | 66 | attached({payload, Payload}, From, State=#state{socket=Socket}) -> 67 | log:event(<<"Send payload">>), 68 | gen_tcp:send(Socket, Payload), 69 | log:event(<<"Transition to recv_response state">>), 70 | {next_state, recv_response, State#state{callback_pid=From}}. 71 | 72 | recv_response(Data, State=#state{buffer=Buffer}) -> 73 | log:event(<<"Received data">>), 74 | NewState = State#state{buffer=[Data|Buffer]}, 75 | {next_state, recv_response, NewState}. 76 | 77 | handle_event({detach, timeout}, _, State=#state{callback_pid=From, socket=Socket}) -> 78 | log:event(<<"Reply with timeout error">>), 79 | gen_fsm:reply(From, {error, timeout}), 80 | gen_tcp:close(Socket), 81 | log:event(<<"Stop normal">>), 82 | {stop, normal, State}; 83 | 84 | handle_event(_Event, StateName, State) -> 85 | {next_state, StateName, State}. 86 | 87 | handle_sync_event(_Event, _From, StateName, State) -> 88 | {next_state, StateName, State}. 89 | 90 | % Handle incoming data from socket 91 | handle_info({tcp, Socket, Data}, StateName, State=#state{callback_pid=From, buffer_size=Size}) -> 92 | NewSize = Size + byte_size(Data), 93 | case NewSize > config:max_output_size() of 94 | false -> 95 | gen_fsm:send_event(self(), Data), 96 | {next_state, StateName, State#state{buffer_size=NewSize}}; 97 | true -> 98 | log:event(<<"Reply with max size error">>), 99 | gen_fsm:reply(From, {error, max_output_size}), 100 | gen_tcp:close(Socket), 101 | log:event(<<"Stop normal">>), 102 | {stop, normal, State} 103 | end; 104 | handle_info({tcp_closed, _}, _StateName, State=#state{callback_pid=From, buffer=Buffer}) -> 105 | log:event(<<"Socket closed">>), 106 | Data = parse_data(Buffer), 107 | log:event(<<"Reply with data">>), 108 | gen_fsm:reply(From, Data), 109 | log:event(<<"Stop normal">>), 110 | {stop, normal, State}; 111 | handle_info(stop, _StateName, State) -> 112 | {stop, normal, State}; 113 | handle_info(_Info, StateName, State) -> 114 | {next_state, StateName, State}. 115 | 116 | code_change(_OldVsn, StateName, State, _Extra) -> 117 | {ok, StateName, State}. 118 | 119 | terminate(Reason, _StateName, _State) -> 120 | Reason. 121 | 122 | raw_container_attach_request(ContainerId) -> 123 | Url = docker_url:container_attach(<<>>, ContainerId, [ 124 | {stdin, true}, {stdout, true}, {stream, true} 125 | ]), 126 | [ 127 | <<"POST ", Url/binary, " HTTP/1.1\r\n">>, 128 | <<"Content-Type: application/vnd.docker.raw-stream\r\n">>, 129 | <<"Connection: Upgrade\r\n">>, 130 | <<"Upgrade: tcp\r\n">>, 131 | <<"Host: 127.0.0.1\r\n">>, 132 | <<"\r\n">> 133 | ]. 134 | 135 | parse_data(Data) -> 136 | parse_data(list_to_binary(lists:reverse(Data)), []). 137 | 138 | parse_data(<<>>, Bodies) -> 139 | format_response(lists:reverse(Bodies)); 140 | parse_data(Data, Bodies) -> 141 | <> = Data, 142 | <> = Rest, 143 | parse_data(Next, [{Type, Body}|Bodies]). 144 | 145 | format_response(Data) -> 146 | format_response( 147 | proplists:get_all_values(1, Data), 148 | proplists:get_all_values(2, Data) 149 | ). 150 | 151 | format_response(Stdout, []) -> 152 | {ok, list_to_binary(Stdout)}; 153 | format_response(_, Stderr) -> 154 | {error, list_to_binary(Stderr)}. 155 | 156 | attach(FsmPid, ContainerId) -> 157 | gen_fsm:sync_send_event(FsmPid, {attach, ContainerId}, ?SYNC_TIMEOUT). 158 | 159 | detach(FsmPid, Reason) -> 160 | gen_fsm:send_all_state_event(FsmPid, {detach, Reason}). 161 | 162 | send(FsmPid, Payload) -> 163 | gen_fsm:sync_send_event(FsmPid, {payload, Payload}, ?SYNC_TIMEOUT). 164 | -------------------------------------------------------------------------------- /apps/glot/src/docker/docker_attach_sup.erl: -------------------------------------------------------------------------------- 1 | -module(docker_attach_sup). 2 | -behaviour(supervisor). 3 | 4 | -export([ 5 | start_link/0, 6 | init/1, 7 | 8 | start_child/0 9 | ]). 10 | 11 | 12 | start_link() -> 13 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 14 | 15 | init([]) -> 16 | MaxRestart = 1200, 17 | MaxTime = 60, 18 | Spec = { 19 | docker_attach, 20 | {docker_attach, start_link, []}, 21 | temporary, 1000, worker, [docker_attach] 22 | }, 23 | 24 | {ok, {{simple_one_for_one, MaxRestart, MaxTime}, [Spec]}}. 25 | 26 | start_child() -> 27 | supervisor:start_child(?MODULE, []). 28 | -------------------------------------------------------------------------------- /apps/glot/src/docker/docker_url.erl: -------------------------------------------------------------------------------- 1 | -module(docker_url). 2 | 3 | -export([ 4 | container_create/1, 5 | container_start/2, 6 | container_remove/3, 7 | container_attach/3 8 | ]). 9 | 10 | container_create(BaseUrl) -> 11 | hackney_url:make_url(BaseUrl, <<"/containers/create">>, []). 12 | 13 | container_start(BaseUrl, Id) -> 14 | hackney_url:make_url(BaseUrl, <<"/containers/", Id/binary, "/start">>, []). 15 | 16 | container_remove(BaseUrl, Id, Params) -> 17 | hackney_url:make_url(BaseUrl, <<"/containers/", Id/binary>>, Params). 18 | 19 | container_attach(BaseUrl, Id, Params) -> 20 | hackney_url:make_url(BaseUrl, <<"/containers/", Id/binary, "/attach">>, Params). 21 | -------------------------------------------------------------------------------- /apps/glot/src/glot.app.src: -------------------------------------------------------------------------------- 1 | {application, glot, [ 2 | {description, "glot - Code Runner API"}, 3 | {vsn, "1.2.1"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, 7 | stdlib, 8 | crypto, 9 | compiler, 10 | syntax_tools, 11 | goldrush, 12 | lager, 13 | cowboy, 14 | jsx, 15 | hackney, 16 | iso8601, 17 | uuid 18 | ]}, 19 | {mod, { glot_app, []}}, 20 | {env, []} 21 | ]}. 22 | -------------------------------------------------------------------------------- /apps/glot/src/glot.erl: -------------------------------------------------------------------------------- 1 | -module(glot). 2 | -export([ 3 | start/0, 4 | start_link/0, 5 | stop/0, 6 | priv_dir/0 7 | ]). 8 | 9 | %% @spec start_link() -> {ok,Pid::pid()} 10 | start_link() -> 11 | [ensure_started(App) || App <- applications()], 12 | lager:info("start_link glot"), 13 | glot_sup:start_link(). 14 | 15 | %% @spec start() -> ok 16 | start() -> 17 | [ensure_started(App) || App <- applications()], 18 | lager:info("start glot"), 19 | application:start(glot). 20 | 21 | %% @spec stop() -> ok 22 | stop() -> 23 | lager:info("stop glot"), 24 | Res = application:stop(glot), 25 | [application:stop(App) || App <- lists:reverse(applications())], 26 | Res. 27 | 28 | applications() -> 29 | [ 30 | syntax_tools, 31 | compiler, 32 | goldrush, 33 | lager, 34 | crypto, 35 | ranch, 36 | cowlib, 37 | cowboy, 38 | jsx, 39 | asn1, 40 | public_key, 41 | ssl, 42 | idna, 43 | hackney, 44 | iso8601, 45 | quickrand, 46 | uuid 47 | ]. 48 | 49 | priv_dir() -> 50 | {ok, App} = application:get_application(?MODULE), 51 | case code:priv_dir(App) of 52 | {error, bad_name} -> 53 | Ebin = filename:dirname(code:which(App)), 54 | filename:join(filename:dirname(Ebin), "priv"); 55 | PrivDir -> 56 | PrivDir 57 | end. 58 | 59 | ensure_started(App) -> 60 | case application:start(App) of 61 | ok -> 62 | ok; 63 | {error, {already_started, App}} -> 64 | ok 65 | end. 66 | -------------------------------------------------------------------------------- /apps/glot/src/glot_app.erl: -------------------------------------------------------------------------------- 1 | -module(glot_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | case config:environment() of 14 | development -> 15 | application:start(sync); 16 | _ -> noop 17 | end, 18 | start_http_server(), 19 | glot_sup:start_link(). 20 | 21 | stop(_State) -> 22 | ok. 23 | 24 | start_http_server() -> 25 | Dispatch = cowboy_router:compile([ 26 | {'_', [ 27 | {"/", root_resource, []}, 28 | {"/admin", admin_root_resource, []}, 29 | {"/admin/users", admin_users_resource, []}, 30 | {"/admin/users/:id", admin_user_resource, []}, 31 | {"/admin/languages", admin_languages_resource, []}, 32 | {"/admin/languages/:id", admin_language_resource, []}, 33 | {"/images", images_resource, []}, 34 | {"/languages", languages_resource, []}, 35 | {"/languages/:name", language_resource, []}, 36 | {"/languages/:name/:version", language_run_resource, []} 37 | ]} 38 | ]), 39 | 40 | {ok, _Pid} = cowboy:start_http(http, 100, 41 | [ 42 | {ip, config:http_listen_ip()}, 43 | {port, config:http_listen_port()} 44 | ], 45 | [ 46 | {env, [{dispatch, Dispatch}]}, 47 | {onrequest, fun http_util:log_request/1}, 48 | {onresponse, fun http_util:log_response/4} 49 | ] 50 | ). 51 | -------------------------------------------------------------------------------- /apps/glot/src/glot_sup.erl: -------------------------------------------------------------------------------- 1 | -module(glot_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | MaxRestart = 600, 27 | MaxTime = 60, 28 | 29 | Children = [ 30 | ?CHILD(docker_attach_sup, supervisor), 31 | ?CHILD(http_log_srv, worker), 32 | ?CHILD(event_log_srv, worker), 33 | ?CHILD(language_srv, worker), 34 | ?CHILD(user_srv, worker) 35 | ], 36 | 37 | {ok, {{one_for_one, MaxRestart, MaxTime}, Children}}. 38 | -------------------------------------------------------------------------------- /apps/glot/src/http/http_auth.erl: -------------------------------------------------------------------------------- 1 | -module(http_auth). 2 | 3 | -export([ 4 | authorize_admin/2, 5 | authorize_user/2 6 | ]). 7 | 8 | -define(WWW_AUTHENTICATE, <<"Token ">>). 9 | -define(MISSING_TOKEN, <<"Missing auth token">>). 10 | -define(WRONG_TOKEN, <<"Wrong auth token">>). 11 | 12 | authorize_admin(Req, State) -> 13 | authorize(Req, State, fun admin_token:is_valid/1). 14 | 15 | authorize_user(Req, State) -> 16 | authorize(Req, State, fun users:valid_token/1). 17 | 18 | authorize(Req, State, ValidateFn) -> 19 | case cowboy_req:parse_header(<<"authorization">>, Req) of 20 | {ok, {<<"token">>, Token}, _} -> 21 | validate_token(Req, State, ValidateFn, Token); 22 | _ -> 23 | Data = jsx:encode(#{message => ?MISSING_TOKEN}), 24 | Req2 = cowboy_req:set_resp_body(Data, Req), 25 | {{false, ?WWW_AUTHENTICATE}, Req2, State} 26 | end. 27 | 28 | validate_token(Req, State, ValidateFn, Token) -> 29 | case ValidateFn(Token) of 30 | true -> 31 | {true, Req, State}; 32 | false -> 33 | Data = jsx:encode(#{message => ?WRONG_TOKEN}), 34 | Req2 = cowboy_req:set_resp_body(Data, Req), 35 | {{false, ?WWW_AUTHENTICATE}, Req2, State} 36 | end. 37 | -------------------------------------------------------------------------------- /apps/glot/src/http/http_util.erl: -------------------------------------------------------------------------------- 1 | -module(http_util). 2 | 3 | -export([ 4 | decode_body/3, 5 | add_cors_headers/1, 6 | log_request/1, 7 | log_response/4 8 | ]). 9 | 10 | -define(INVALID_JSON, <<"Invalid json">>). 11 | 12 | log_request(Req) -> 13 | {Headers, _} = cowboy_req:headers(Req), 14 | {Method, _} = cowboy_req:method(Req), 15 | {Path, _} = cowboy_req:path(Req), 16 | {{Ip, Port}, _} = cowboy_req:peer(Req), 17 | log:http(#{ 18 | peer => #{ 19 | ip => list_to_binary(inet:ntoa(Ip)), 20 | port => Port 21 | }, 22 | headers => Headers, 23 | method => Method, 24 | path => Path, 25 | type => request 26 | }), 27 | Req. 28 | 29 | log_request_body(Body) -> 30 | log_body(Body, request_body). 31 | 32 | log_response_body(Body) -> 33 | log_body(Body, response_body). 34 | 35 | log_body(<<>>, _) -> 36 | ok; 37 | log_body(Body, Type) -> 38 | log:http(#{ 39 | body => Body, 40 | type => Type 41 | }). 42 | 43 | log_response(Status, Headers, Body, Req) -> 44 | log:http(#{ 45 | status => Status, 46 | headers => format_headers(Headers), 47 | type => response 48 | }), 49 | log_response_body(Body), 50 | Req. 51 | 52 | format_headers([]) -> 53 | []; 54 | format_headers([{K, V}|Rest]) when is_list(V) -> 55 | [{K, list_to_binary(V)}|format_headers(Rest)]; 56 | format_headers([{K, V}|Rest]) -> 57 | [{K, V}|format_headers(Rest)]. 58 | 59 | decode_body(F, Req, State) -> 60 | {ok, Body, Req2} = cowboy_req:body(Req), 61 | log_request_body(Body), 62 | case jsx:is_json(Body) of 63 | true -> 64 | Data = jsx:decode(Body), 65 | F(Data, Req2, State); 66 | false -> 67 | Payload = jsx:encode([{message, ?INVALID_JSON}]), 68 | {ok, Req3} = cowboy_req:reply(400, [], Payload, Req2), 69 | {halt, Req3, State} 70 | end. 71 | 72 | add_cors_headers(Req) -> 73 | Headers = [ 74 | {<<"access-control-allow-methods">>, <<"POST, OPTIONS">>}, 75 | {<<"access-control-allow-origin">>, <<"*">>}, 76 | {<<"access-control-allow-headers">>, <<"Content-Type">>} 77 | ], 78 | set_headers(Headers, Req). 79 | 80 | set_headers(Headers, Req) -> 81 | lists:foldl(fun({Name, Value}, R) -> 82 | cowboy_req:set_resp_header(Name, Value, R) 83 | end, Req, Headers). 84 | -------------------------------------------------------------------------------- /apps/glot/src/logging/event_log_srv.erl: -------------------------------------------------------------------------------- 1 | -module(event_log_srv). 2 | -behaviour(gen_server). 3 | -export([ 4 | start_link/0, 5 | stop/0, 6 | init/1, 7 | handle_call/3, 8 | handle_cast/2, 9 | handle_info/2, 10 | code_change/3, 11 | terminate/2, 12 | 13 | append/1 14 | ]). 15 | 16 | -record(state, { 17 | file 18 | }). 19 | 20 | start_link() -> 21 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 22 | 23 | init([]) -> 24 | Path = config:event_log_path(), 25 | {ok, File} = file:open(Path, [append, delayed_write, {encoding, utf8}]), 26 | {ok, #state{file=File}}. 27 | 28 | stop() -> 29 | gen_server:call(?MODULE, stop). 30 | 31 | handle_call(_Event, _From, State) -> 32 | {noreply, State}. 33 | 34 | handle_cast({append, Data}, State=#state{file=File}) -> 35 | log:write(File, Data), 36 | {noreply, State}; 37 | handle_cast(_Event, State) -> 38 | {noreply, State}. 39 | 40 | handle_info(_Event, State) -> 41 | {noreply, State}. 42 | 43 | code_change(_OldVsc, State, _Extra) -> 44 | {ok, State}. 45 | 46 | terminate(Reason, #state{file=File}) -> 47 | file:close(File), 48 | Reason. 49 | 50 | append(Data) -> 51 | Data2 = Data#{ 52 | timestamp => iso8601:format(os:timestamp()), 53 | pid => util:pid_to_binary(self()) 54 | }, 55 | gen_server:cast(?MODULE, {append, Data2}). 56 | -------------------------------------------------------------------------------- /apps/glot/src/logging/http_log_srv.erl: -------------------------------------------------------------------------------- 1 | -module(http_log_srv). 2 | -behaviour(gen_server). 3 | -export([ 4 | start_link/0, 5 | stop/0, 6 | init/1, 7 | handle_call/3, 8 | handle_cast/2, 9 | handle_info/2, 10 | code_change/3, 11 | terminate/2, 12 | 13 | append/1 14 | ]). 15 | 16 | -record(state, { 17 | file 18 | }). 19 | 20 | start_link() -> 21 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 22 | 23 | init([]) -> 24 | Path = config:http_log_path(), 25 | {ok, File} = file:open(Path, [append, delayed_write, {encoding, utf8}]), 26 | {ok, #state{file=File}}. 27 | 28 | stop() -> 29 | gen_server:call(?MODULE, stop). 30 | 31 | handle_call(_Event, _From, State) -> 32 | {noreply, State}. 33 | 34 | handle_cast({append, Data}, State=#state{file=File}) -> 35 | log:write(File, Data), 36 | {noreply, State}; 37 | handle_cast(_Event, State) -> 38 | {noreply, State}. 39 | 40 | handle_info(_Event, State) -> 41 | {noreply, State}. 42 | 43 | code_change(_OldVsc, State, _Extra) -> 44 | {ok, State}. 45 | 46 | terminate(Reason, #state{file=File}) -> 47 | file:close(File), 48 | Reason. 49 | 50 | append(Data) -> 51 | Data2 = Data#{ 52 | timestamp => iso8601:format(os:timestamp()), 53 | pid => util:pid_to_binary(self()) 54 | }, 55 | gen_server:cast(?MODULE, {append, Data2}). 56 | -------------------------------------------------------------------------------- /apps/glot/src/logging/log.erl: -------------------------------------------------------------------------------- 1 | -module(log). 2 | -export([ 3 | http/1, 4 | event/1, 5 | write/2 6 | ]). 7 | 8 | event(Event) when is_list(Event) -> 9 | event(list_to_binary(Event)); 10 | event(Event) -> 11 | event_log_srv:append(#{event => Event}). 12 | 13 | http(Http) -> 14 | http_log_srv:append(Http). 15 | 16 | write(File, Data) -> 17 | Json = jsx:encode(Data), 18 | file:write(File, <>). 19 | -------------------------------------------------------------------------------- /apps/glot/src/models/admin_token.erl: -------------------------------------------------------------------------------- 1 | -module(admin_token). 2 | 3 | -export([ 4 | is_valid/1 5 | ]). 6 | 7 | is_valid(Token) -> 8 | config:admin_token() =:= Token. 9 | -------------------------------------------------------------------------------- /apps/glot/src/models/language.erl: -------------------------------------------------------------------------------- 1 | -module(language). 2 | 3 | -export([ 4 | get/1, 5 | exists/1, 6 | save/3, 7 | delete/1, 8 | list/0, 9 | list_names/0, 10 | list_images/0, 11 | list_versions/1, 12 | get_image/2, 13 | is_supported/1, 14 | is_supported/2 15 | ]). 16 | 17 | identifier(Name, Version) -> 18 | util:sha1(<>). 19 | 20 | save(Name, Version, Image) -> 21 | Id = identifier(Name, Version), 22 | language_srv:save(Id, {Name, Version, Image}). 23 | 24 | delete(Id) -> 25 | language_srv:delete(Id). 26 | 27 | get(Id) -> 28 | {ok, {Name, Vsn, Image}} = maps:find(Id, language_srv:list()), 29 | {Id, Name, Vsn, Image}. 30 | 31 | exists(Id) -> 32 | maps:is_key(Id, language_srv:list()). 33 | 34 | is_supported(Name) -> 35 | lists:member(Name, list_names()). 36 | 37 | is_supported(Name, Version) -> 38 | Id = identifier(Name, Version), 39 | maps:is_key(Id, language_srv:list()). 40 | 41 | list() -> 42 | maps:fold(fun(Id, {Name, Vsn, Image}, Acc) -> 43 | [{Id, Name, Vsn, Image}|Acc] 44 | end, [], language_srv:list()). 45 | 46 | list_names() -> 47 | Names = maps:fold(fun(_, {Name, _, _}, Acc) -> 48 | [Name|Acc] 49 | end, [], language_srv:list()), 50 | sort_and_remove_duplicates(Names). 51 | 52 | list_images() -> 53 | Images = maps:fold(fun(_, {_, _, Image}, Acc) -> 54 | [Image|Acc] 55 | end, [], language_srv:list()), 56 | sort_and_remove_duplicates(Images). 57 | 58 | list_versions(Name) -> 59 | Versions = maps:fold(fun(_, {LangName, Vsn, _}, Acc) -> 60 | case LangName =:= Name of 61 | true -> [Vsn|Acc]; 62 | false -> Acc 63 | end 64 | end, [], language_srv:list()), 65 | sort_and_remove_duplicates(Versions). 66 | 67 | get_image(Name, Version) -> 68 | Id = identifier(Name, Version), 69 | {ok, {_, _, Image}} = maps:find(Id, language_srv:list()), 70 | Image. 71 | 72 | sort_and_remove_duplicates(List) -> 73 | Set = gb_sets:from_list(List), 74 | gb_sets:to_list(Set). 75 | -------------------------------------------------------------------------------- /apps/glot/src/models/language_run.erl: -------------------------------------------------------------------------------- 1 | -module(language_run). 2 | 3 | -export([ 4 | run/3 5 | ]). 6 | 7 | run(Language, Version, Data) -> 8 | Image = language:get_image(Language, Version), 9 | Config = config:docker_container_config(Image), 10 | log:event(<<"Create container from image ", Image/binary>>), 11 | ContainerId = docker:container_create(Config), 12 | RemoveRef = remove_after(config:docker_run_timeout() + 5, ContainerId), 13 | log:event(<<"Start container ", ContainerId/binary>>), 14 | docker:container_start(ContainerId), 15 | log:event(<<"Attach container ", ContainerId/binary>>), 16 | Pid = docker:container_attach(ContainerId), 17 | DetachRef = detach_timeout_after(config:docker_run_timeout(), Pid), 18 | Payload = prepare_payload(Language, Data), 19 | log:event([<<"Send payload to ">>, ContainerId, <<" via ">>, util:pid_to_binary(Pid)]), 20 | Res = docker:container_send(Pid, Payload), 21 | [cancel_timer(X) || X <- [DetachRef, RemoveRef]], 22 | log:event(<<"Remove container ", ContainerId/binary>>), 23 | docker:container_remove(ContainerId), 24 | log:event(<<"Returning result">>), 25 | Res. 26 | 27 | remove_after(Seconds, ContainerId) -> 28 | {ok, Ref} = timer:apply_after( 29 | Seconds * 1000, 30 | docker, 31 | container_remove, 32 | [ContainerId] 33 | ), 34 | Ref. 35 | 36 | detach_timeout_after(Seconds, Pid) -> 37 | {ok, Ref} = timer:apply_after( 38 | Seconds * 1000, 39 | docker, 40 | container_detach, 41 | [Pid, timeout] 42 | ), 43 | Ref. 44 | 45 | cancel_timer(Ref) -> 46 | timer:cancel(Ref). 47 | 48 | prepare_payload(Language, Data) -> 49 | jsx:encode(#{ 50 | <<"language">> => Language, 51 | <<"files">> => proplists:get_value(<<"files">>, Data, []), 52 | <<"stdin">> => proplists:get_value(<<"stdin">>, Data, <<"">>), 53 | <<"command">> => proplists:get_value(<<"command">>, Data, <<"">>) 54 | }). 55 | -------------------------------------------------------------------------------- /apps/glot/src/models/users.erl: -------------------------------------------------------------------------------- 1 | -module(users). 2 | -export([ 3 | get/1, 4 | list/0, 5 | save/1, 6 | update/2, 7 | delete/1, 8 | valid_token/1 9 | ]). 10 | 11 | identifier() -> 12 | Uuid = uuid:uuid_to_string(uuid:get_v4()), 13 | list_to_binary(Uuid). 14 | 15 | valid_token(Token) -> 16 | case user_srv:get_by_token(Token) of 17 | [] -> false; 18 | [_] -> true 19 | end. 20 | 21 | get(Id) -> 22 | user_srv:get(Id). 23 | 24 | list() -> 25 | user_srv:list(). 26 | 27 | save(Data) -> 28 | User = prepare_save(Data), 29 | user_srv:save(User). 30 | 31 | update(OldUser, NewUser) -> 32 | User = prepare_update(OldUser, NewUser), 33 | user_srv:save(User). 34 | 35 | delete(Id) -> 36 | user_srv:delete(Id). 37 | 38 | prepare_save(Data) -> 39 | Now = iso8601:format(os:timestamp()), 40 | Data#{ 41 | id => identifier(), 42 | created => Now, 43 | modified => Now 44 | }. 45 | 46 | prepare_update(OldUser, NewUser) -> 47 | User = maps:merge(OldUser, NewUser), 48 | Now = iso8601:format(os:timestamp()), 49 | User#{ 50 | modified := Now 51 | }. 52 | -------------------------------------------------------------------------------- /apps/glot/src/persistent/language_srv.erl: -------------------------------------------------------------------------------- 1 | -module(language_srv). 2 | -behaviour(gen_server). 3 | -export([ 4 | start_link/0, 5 | stop/0, 6 | init/1, 7 | handle_call/3, 8 | handle_cast/2, 9 | handle_info/2, 10 | code_change/3, 11 | terminate/2, 12 | 13 | list/0, 14 | save/2, 15 | delete/1 16 | ]). 17 | 18 | -record(state, { 19 | filename, 20 | languages 21 | }). 22 | 23 | start_link() -> 24 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 25 | 26 | init([]) -> 27 | Fname = config:languages_data_path(), 28 | {ok, Langs} = persist:load(Fname, maps:new()), 29 | {ok, #state{filename=Fname, languages=Langs}}. 30 | 31 | stop() -> 32 | gen_server:call(?MODULE, stop). 33 | 34 | handle_call({list}, _, State=#state{languages=Langs}) -> 35 | {reply, Langs, State}; 36 | handle_call({save, Id, Language}, _, State=#state{languages=Langs, filename=Fname}) -> 37 | NewLangs = maps:put(Id, Language, Langs), 38 | ok = persist:save(Fname, NewLangs), 39 | {reply, Id, State#state{languages=NewLangs}}; 40 | handle_call({delete, Id}, _, State=#state{languages=Langs, filename=Fname}) -> 41 | NewLangs = maps:remove(Id, Langs), 42 | ok = persist:save(Fname, NewLangs), 43 | {reply, ok, State#state{languages=NewLangs}}; 44 | handle_call(_Event, _From, State) -> 45 | {noreply, State}. 46 | 47 | handle_cast(_Event, State) -> 48 | {noreply, State}. 49 | 50 | handle_info(_Event, State) -> 51 | {noreply, State}. 52 | 53 | code_change(_OldVsc, State, _Extra) -> 54 | {ok, State}. 55 | 56 | terminate(Reason, _State) -> 57 | Reason. 58 | 59 | list() -> 60 | gen_server:call(?MODULE, {list}). 61 | 62 | save(Id, Language) -> 63 | gen_server:call(?MODULE, {save, Id, Language}). 64 | 65 | delete(Id) -> 66 | gen_server:call(?MODULE, {delete, Id}). 67 | -------------------------------------------------------------------------------- /apps/glot/src/persistent/persist.erl: -------------------------------------------------------------------------------- 1 | -module(persist). 2 | -export([ 3 | load/2, 4 | save/2 5 | ]). 6 | 7 | 8 | load(Fname, Default) -> 9 | case file:read_file(Fname) of 10 | {ok, Binary} -> {ok, binary_to_term(Binary)}; 11 | {error, enoent} -> {ok, Default}; 12 | Error -> Error 13 | end. 14 | 15 | save(Dst, Data) -> 16 | Src = tmp_fname(Dst), 17 | case file:write_file(Src, term_to_binary(Data)) of 18 | ok -> file:rename(Src, Dst); 19 | Error -> Error 20 | end. 21 | 22 | tmp_fname(Path) -> 23 | filename:join( 24 | filename:dirname(Path), 25 | "." ++ filename:basename(Path) ++ ".tmp" 26 | ). 27 | -------------------------------------------------------------------------------- /apps/glot/src/persistent/user_srv.erl: -------------------------------------------------------------------------------- 1 | -module(user_srv). 2 | -behaviour(gen_server). 3 | -export([ 4 | start_link/0, 5 | stop/0, 6 | init/1, 7 | handle_call/3, 8 | handle_cast/2, 9 | handle_info/2, 10 | code_change/3, 11 | terminate/2, 12 | 13 | get/1, 14 | list/0, 15 | save/1, 16 | delete/1, 17 | get_by_token/1 18 | ]). 19 | 20 | -record(state, { 21 | filename, 22 | users 23 | }). 24 | 25 | start_link() -> 26 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 27 | 28 | init([]) -> 29 | Fname = config:users_data_path(), 30 | {ok, Users} = persist:load(Fname, maps:new()), 31 | {ok, #state{filename=Fname, users=Users}}. 32 | 33 | stop() -> 34 | gen_server:call(?MODULE, stop). 35 | 36 | handle_call({get, Id}, _, State=#state{users=Users}) -> 37 | Res = case maps:find(Id, Users) of 38 | {ok, User} -> {ok, User}; 39 | error -> {error, not_found} 40 | end, 41 | {reply, Res, State}; 42 | handle_call({get_by_token, Token}, _, State=#state{users=Users}) -> 43 | Found = maps:fold(fun(_, User, Acc) -> 44 | case Token == maps:get(token, User) of 45 | true -> [User|Acc]; 46 | false -> Acc 47 | end 48 | end, [], Users), 49 | {reply, Found, State}; 50 | handle_call({list}, _, State=#state{users=Users}) -> 51 | {reply, maps:values(Users), State}; 52 | handle_call({save, Id, User}, _, State=#state{users=Users, filename=Fname}) -> 53 | NewUsers = maps:put(Id, User, Users), 54 | ok = persist:save(Fname, NewUsers), 55 | {reply, User, State#state{users=NewUsers}}; 56 | handle_call({delete, Id}, _, State=#state{users=Users, filename=Fname}) -> 57 | NewUsers = maps:remove(Id, Users), 58 | ok = persist:save(Fname, NewUsers), 59 | {reply, ok, State#state{users=NewUsers}}; 60 | handle_call(_Event, _From, State) -> 61 | {noreply, State}. 62 | 63 | handle_cast(_Event, State) -> 64 | {noreply, State}. 65 | 66 | handle_info(_Event, State) -> 67 | {noreply, State}. 68 | 69 | code_change(_OldVsc, State, _Extra) -> 70 | {ok, State}. 71 | 72 | terminate(Reason, _State) -> 73 | Reason. 74 | 75 | get(Id) -> 76 | gen_server:call(?MODULE, {get, Id}). 77 | 78 | get_by_token(Token) -> 79 | gen_server:call(?MODULE, {get_by_token, Token}). 80 | 81 | list() -> 82 | gen_server:call(?MODULE, {list}). 83 | 84 | save(User) -> 85 | Id = maps:get(id, User), 86 | gen_server:call(?MODULE, {save, Id, User}). 87 | 88 | delete(Id) -> 89 | gen_server:call(?MODULE, {delete, Id}). 90 | -------------------------------------------------------------------------------- /apps/glot/src/resources/admin_language_resource.erl: -------------------------------------------------------------------------------- 1 | -module(admin_language_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_accepted/2, 7 | content_types_provided/2, 8 | is_authorized/2, 9 | resource_exists/2, 10 | delete_resource/2, 11 | get_language/2 12 | ]). 13 | 14 | -record(state, { 15 | id 16 | }). 17 | 18 | init(_Transport, _Req, _Opts) -> 19 | {upgrade, protocol, cowboy_rest}. 20 | 21 | rest_init(Req, []) -> 22 | {ok, Req, #state{}}. 23 | 24 | allowed_methods(Req, State) -> 25 | Methods = [<<"GET">>, <<"DELETE">>], 26 | {Methods, Req, State}. 27 | 28 | content_types_accepted(Req, State) -> 29 | Handlers = [ 30 | {{<<"application">>, <<"json">>, '*'}, noop} 31 | ], 32 | {Handlers, Req, State}. 33 | 34 | content_types_provided(Req, State) -> 35 | Handlers = [ 36 | {{<<"application">>, <<"json">>, '*'}, get_language} 37 | ], 38 | {Handlers, Req, State}. 39 | 40 | is_authorized(Req, State) -> 41 | http_auth:authorize_admin(Req, State). 42 | 43 | resource_exists(Req, State) -> 44 | {Id, _} = cowboy_req:binding(id, Req), 45 | 46 | case language:exists(Id) of 47 | true -> 48 | {true, Req, State#state{id=Id}}; 49 | false -> 50 | {false, Req, State} 51 | end. 52 | 53 | get_language(Req, State=#state{id=Id}) -> 54 | Language = language_tuple_to_map(language:get(Id)), 55 | {jsx:encode(Language), Req, State}. 56 | 57 | 58 | delete_resource(Req, State=#state{id=Id}) -> 59 | language:delete(Id), 60 | {true, Req, State}. 61 | 62 | language_tuple_to_map({Id, Name, Version, Image}) -> 63 | #{ 64 | id => Id, 65 | name => Name, 66 | version => Version, 67 | image => Image 68 | }. 69 | -------------------------------------------------------------------------------- /apps/glot/src/resources/admin_languages_resource.erl: -------------------------------------------------------------------------------- 1 | -module(admin_languages_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_accepted/2, 7 | content_types_provided/2, 8 | is_authorized/2, 9 | list_languages/2, 10 | accept_put/2 11 | ]). 12 | 13 | 14 | init(_Transport, _Req, _Opts) -> 15 | {upgrade, protocol, cowboy_rest}. 16 | 17 | rest_init(Req, []) -> 18 | {ok, Req, []}. 19 | 20 | allowed_methods(Req, State) -> 21 | Methods = [<<"GET">>, <<"PUT">>], 22 | {Methods, Req, State}. 23 | 24 | content_types_accepted(Req, State) -> 25 | Handlers = [ 26 | {{<<"application">>, <<"json">>, '*'}, accept_put} 27 | ], 28 | {Handlers, Req, State}. 29 | 30 | content_types_provided(Req, State) -> 31 | Handlers = [ 32 | {{<<"application">>, <<"json">>, '*'}, list_languages} 33 | ], 34 | {Handlers, Req, State}. 35 | 36 | is_authorized(Req, State) -> 37 | http_auth:authorize_admin(Req, State). 38 | 39 | list_languages(Req, State) -> 40 | Languages = [language_tuple_to_map(X) || X <- language:list()], 41 | {jsx:encode(Languages), Req, State}. 42 | 43 | accept_put(Req, State) -> 44 | http_util:decode_body(fun save_language/3, Req, State). 45 | 46 | save_language(Data, Req, State) -> 47 | % TODO: Ensure that all values are defined, i.e. not undefined 48 | {Name, Vsn, Image} = proplist_to_language_tuple(Data), 49 | Id = language:save(Name, Vsn, Image), 50 | LanguageMap = language_tuple_to_map(language:get(Id)), 51 | Req2 = cowboy_req:set_resp_body(jsx:encode(LanguageMap), Req), 52 | {true, Req2, State}. 53 | 54 | language_tuple_to_map({Id, Name, Version, Image}) -> 55 | BaseUrl = config:base_url(), 56 | #{ 57 | id => Id, 58 | name => Name, 59 | version => Version, 60 | image => Image, 61 | url => <> 62 | }. 63 | 64 | proplist_to_language_tuple(Data) -> 65 | { 66 | proplists:get_value(<<"name">>, Data), 67 | proplists:get_value(<<"version">>, Data), 68 | proplists:get_value(<<"image">>, Data) 69 | }. 70 | -------------------------------------------------------------------------------- /apps/glot/src/resources/admin_root_resource.erl: -------------------------------------------------------------------------------- 1 | -module(admin_root_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_provided/2, 7 | root/2 8 | ]). 9 | 10 | init(_Transport, _Req, _Opts) -> 11 | {upgrade, protocol, cowboy_rest}. 12 | 13 | rest_init(Req, []) -> 14 | {ok, Req, []}. 15 | 16 | allowed_methods(Req, State) -> 17 | {[<<"GET">>], Req, State}. 18 | 19 | content_types_provided(Req, State) -> 20 | Handlers = [ 21 | {{<<"application">>, <<"json">>, '*'}, root} 22 | ], 23 | {Handlers, Req, State}. 24 | 25 | root(Req, State) -> 26 | BaseUrl = config:base_url(), 27 | Data = #{ 28 | urls => #{ 29 | users => <>, 30 | languages => <> 31 | } 32 | }, 33 | {jsx:encode(Data), Req, State}. 34 | -------------------------------------------------------------------------------- /apps/glot/src/resources/admin_user_resource.erl: -------------------------------------------------------------------------------- 1 | -module(admin_user_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_accepted/2, 7 | content_types_provided/2, 8 | is_authorized/2, 9 | resource_exists/2, 10 | get/2, 11 | accept_put/2, 12 | delete_resource/2 13 | ]). 14 | 15 | -record(state, { 16 | id, 17 | user 18 | }). 19 | 20 | init(_Transport, _Req, _Opts) -> 21 | {upgrade, protocol, cowboy_rest}. 22 | 23 | rest_init(Req, []) -> 24 | {ok, Req, #state{}}. 25 | 26 | allowed_methods(Req, State) -> 27 | {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}. 28 | 29 | content_types_accepted(Req, State) -> 30 | Handlers = [ 31 | {{<<"application">>, <<"json">>, '*'}, accept_put} 32 | ], 33 | {Handlers, Req, State}. 34 | 35 | content_types_provided(Req, State) -> 36 | Handlers = [ 37 | {{<<"application">>, <<"json">>, '*'}, get} 38 | ], 39 | {Handlers, Req, State}. 40 | 41 | is_authorized(Req, State) -> 42 | http_auth:authorize_admin(Req, State). 43 | 44 | resource_exists(Req, State) -> 45 | {Id, _} = cowboy_req:binding(id, Req), 46 | case users:get(Id) of 47 | {ok, User} -> 48 | {true, Req, #state{id=Id, user=User}}; 49 | {error, not_found} -> 50 | {false, Req, State} 51 | end. 52 | 53 | get(Req, State=#state{user=User}) -> 54 | {prepare_response(User), Req, State}. 55 | 56 | accept_put(Req, State) -> 57 | http_util:decode_body(fun update_user/3, Req, State). 58 | 59 | update_user(Data, Req, State=#state{user=User}) -> 60 | NewUser = users:update(User, normalize(Data)), 61 | Req2 = cowboy_req:set_resp_body(prepare_response(NewUser), Req), 62 | {true, Req2, State}. 63 | 64 | delete_resource(Req, State=#state{id=Id}) -> 65 | users:delete(Id), 66 | {true, Req, State}. 67 | 68 | normalize(Data) -> 69 | #{ 70 | token => proplists:get_value(<<"token">>, Data, <<>>) 71 | }. 72 | 73 | prepare_response(User) -> 74 | jsx:encode(format_user(User)). 75 | 76 | format_user(User) -> 77 | Id = maps:get(id, User), 78 | User#{ 79 | url => get_url(Id) 80 | }. 81 | 82 | get_url(Id) -> 83 | BaseUrl = config:base_url(), 84 | <>. 85 | -------------------------------------------------------------------------------- /apps/glot/src/resources/admin_users_resource.erl: -------------------------------------------------------------------------------- 1 | -module(admin_users_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_accepted/2, 7 | content_types_provided/2, 8 | is_authorized/2, 9 | list/2, 10 | accept_post/2 11 | ]). 12 | 13 | 14 | init(_Transport, _Req, _Opts) -> 15 | {upgrade, protocol, cowboy_rest}. 16 | 17 | rest_init(Req, []) -> 18 | {ok, Req, []}. 19 | 20 | allowed_methods(Req, State) -> 21 | {[<<"GET">>, <<"POST">>], Req, State}. 22 | 23 | content_types_accepted(Req, State) -> 24 | Handlers = [ 25 | {{<<"application">>, <<"json">>, '*'}, accept_post} 26 | ], 27 | {Handlers, Req, State}. 28 | 29 | content_types_provided(Req, State) -> 30 | Handlers = [ 31 | {{<<"application">>, <<"json">>, '*'}, list} 32 | ], 33 | {Handlers, Req, State}. 34 | 35 | is_authorized(Req, State) -> 36 | http_auth:authorize_admin(Req, State). 37 | 38 | list(Req, State) -> 39 | Users = users:list(), 40 | {prepare_list_response(Users), Req, State}. 41 | 42 | accept_post(Req, State) -> 43 | http_util:decode_body(fun save/3, Req, State). 44 | 45 | save(Data, Req, State) -> 46 | User = users:save(normalize(Data)), 47 | Req2 = cowboy_req:set_resp_body(prepare_response(User), Req), 48 | {true, Req2, State}. 49 | 50 | normalize(Data) -> 51 | #{ 52 | token => proplists:get_value(<<"token">>, Data, <<>>) 53 | }. 54 | 55 | prepare_list_response(Users) -> 56 | jsx:encode([format_user(X) || X <- Users]). 57 | 58 | prepare_response(User) -> 59 | jsx:encode(format_user(User)). 60 | 61 | format_user(User) -> 62 | Id = maps:get(id, User), 63 | User#{ 64 | url => get_url(Id) 65 | }. 66 | 67 | get_url(Id) -> 68 | BaseUrl = config:base_url(), 69 | <>. 70 | -------------------------------------------------------------------------------- /apps/glot/src/resources/images_resource.erl: -------------------------------------------------------------------------------- 1 | -module(images_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_provided/2, 7 | list_images/2 8 | ]). 9 | 10 | init(_Transport, _Req, _Opts) -> 11 | {upgrade, protocol, cowboy_rest}. 12 | 13 | rest_init(Req, []) -> 14 | {ok, Req, []}. 15 | 16 | allowed_methods(Req, State) -> 17 | Methods = [<<"GET">>], 18 | {Methods, Req, State}. 19 | 20 | content_types_provided(Req, State) -> 21 | Handlers = [ 22 | {{<<"application">>, <<"json">>, '*'}, list_images} 23 | ], 24 | {Handlers, Req, State}. 25 | 26 | list_images(Req, State) -> 27 | Images = [format_image(X) || X <- language:list_images()], 28 | {jsx:encode(Images), Req, State}. 29 | 30 | format_image(Image) -> 31 | BaseUrl = <<"https://hub.docker.com/r/">>, 32 | [Name, _] = binary:split(Image, <<":">>), 33 | #{ 34 | image => Image, 35 | url => <> 36 | }. 37 | -------------------------------------------------------------------------------- /apps/glot/src/resources/language_resource.erl: -------------------------------------------------------------------------------- 1 | -module(language_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_provided/2, 7 | resource_exists/2, 8 | list_versions/2 9 | ]). 10 | 11 | -record(state, { 12 | name 13 | }). 14 | 15 | init(_Transport, _Req, _Opts) -> 16 | {upgrade, protocol, cowboy_rest}. 17 | 18 | rest_init(Req, []) -> 19 | {ok, Req, #state{}}. 20 | 21 | allowed_methods(Req, State) -> 22 | Methods = [<<"GET">>], 23 | {Methods, Req, State}. 24 | 25 | content_types_provided(Req, State) -> 26 | Handlers = [ 27 | {{<<"application">>, <<"json">>, '*'}, list_versions} 28 | ], 29 | {Handlers, Req, State}. 30 | 31 | resource_exists(Req, State) -> 32 | {Name, _} = cowboy_req:binding(name, Req), 33 | 34 | case language:is_supported(Name) of 35 | true -> 36 | {true, Req, State#state{name=Name}}; 37 | false -> 38 | {false, Req, State} 39 | end. 40 | 41 | list_versions(Req, State=#state{name=Name}) -> 42 | Versions = [format_version(Name, X) || X <- language:list_versions(Name)], 43 | {jsx:encode(Versions), Req, State}. 44 | 45 | format_version(Name, Version) -> 46 | BaseUrl = config:base_url(), 47 | #{ 48 | version => Version, 49 | url => <> 50 | }. 51 | -------------------------------------------------------------------------------- /apps/glot/src/resources/language_run_resource.erl: -------------------------------------------------------------------------------- 1 | -module(language_run_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_accepted/2, 7 | content_types_provided/2, 8 | is_authorized/2, 9 | allow_missing_post/2, 10 | resource_exists/2, 11 | accept_post/2 12 | ]). 13 | 14 | -define(MISSING_FILES, <<"Missing files">>). 15 | -define(INCOMPLETE_FILE, <<"One or more files are incomplete">>). 16 | -define(TIMEOUT_ERROR, <<"Code exceeded the maximum allowed running time">>). 17 | -define(MAX_OUTPUT_SIZE_ERROR, <<"Output exceeded the maximum allowed size">>). 18 | 19 | -record(state, { 20 | name, 21 | version 22 | }). 23 | 24 | 25 | init(_Transport, _Req, _Opts) -> 26 | {upgrade, protocol, cowboy_rest}. 27 | 28 | rest_init(Req, []) -> 29 | {ok, Req, #state{}}. 30 | 31 | allowed_methods(Req, State) -> 32 | Methods = [<<"POST">>], 33 | {Methods, Req, State}. 34 | 35 | content_types_accepted(Req, State) -> 36 | Handlers = [ 37 | {{<<"application">>, <<"json">>, '*'}, accept_post} 38 | ], 39 | {Handlers, Req, State}. 40 | 41 | content_types_provided(Req, State) -> 42 | Handlers = [ 43 | {{<<"application">>, <<"json">>, '*'}, noop} 44 | ], 45 | {Handlers, Req, State}. 46 | 47 | is_authorized(Req, State) -> 48 | http_auth:authorize_user(Req, State). 49 | 50 | allow_missing_post(Req, State) -> 51 | {false, Req, State}. 52 | 53 | resource_exists(Req, State) -> 54 | {Name, _} = cowboy_req:binding(name, Req), 55 | {Vsn, _} = cowboy_req:binding(version, Req), 56 | 57 | case language:is_supported(Name, Vsn) of 58 | true -> 59 | {true, Req, State#state{name=Name, version=Vsn}}; 60 | false -> 61 | {false, Req, State} 62 | end. 63 | 64 | accept_post(Req, State) -> 65 | http_util:decode_body(fun validate_and_run_code/3, Req, State). 66 | 67 | validate_and_run_code(Data, Req, State) -> 68 | case validate_files(Data) of 69 | ok -> 70 | run_code(Data, Req, State); 71 | {error, missing_files} -> 72 | Res = jsx:encode(#{<<"message">> => ?MISSING_FILES}), 73 | {false, cowboy_req:set_resp_body(Res, Req), State}; 74 | {error, incomplete_file} -> 75 | Res = jsx:encode(#{<<"message">> => ?INCOMPLETE_FILE}), 76 | {false, cowboy_req:set_resp_body(Res, Req), State} 77 | end. 78 | 79 | validate_files(Data) -> 80 | case proplists:get_value(<<"files">>, Data, []) of 81 | [] -> {error, missing_files}; 82 | Files -> ensure_complete_files(Files) 83 | end. 84 | 85 | ensure_complete_files(Files) -> 86 | Incomplete = lists:any(fun(File) -> 87 | Name = proplists:get_value(<<"name">>, File, missing), 88 | Content = proplists:get_value(<<"content">>, File, missing), 89 | Name =:= missing orelse Content =:= missing 90 | end, Files), 91 | case Incomplete of 92 | true -> {error, incomplete_file}; 93 | false -> ok 94 | end. 95 | 96 | run_code(Data, Req, State=#state{name=Name, version=Vsn}) -> 97 | case language_run:run(Name, Vsn, Data) of 98 | {ok, Res} -> 99 | {true, cowboy_req:set_resp_body(Res, Req), State}; 100 | {error, timeout} -> 101 | Res = jsx:encode(#{<<"message">> => ?TIMEOUT_ERROR}), 102 | {false, cowboy_req:set_resp_body(Res, Req), State}; 103 | {error, max_output_size} -> 104 | Res = jsx:encode(#{<<"message">> => ?MAX_OUTPUT_SIZE_ERROR}), 105 | {false, cowboy_req:set_resp_body(Res, Req), State}; 106 | {error, Msg} -> 107 | Res = jsx:encode(#{<<"message">> => Msg}), 108 | {false, cowboy_req:set_resp_body(Res, Req), State} 109 | end. 110 | -------------------------------------------------------------------------------- /apps/glot/src/resources/languages_resource.erl: -------------------------------------------------------------------------------- 1 | -module(languages_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_provided/2, 7 | list_names/2 8 | ]). 9 | 10 | init(_Transport, _Req, _Opts) -> 11 | {upgrade, protocol, cowboy_rest}. 12 | 13 | rest_init(Req, []) -> 14 | {ok, Req, []}. 15 | 16 | allowed_methods(Req, State) -> 17 | Methods = [<<"GET">>], 18 | {Methods, Req, State}. 19 | 20 | content_types_provided(Req, State) -> 21 | Handlers = [ 22 | {{<<"application">>, <<"json">>, '*'}, list_names} 23 | ], 24 | {Handlers, Req, State}. 25 | 26 | list_names(Req, State) -> 27 | Names = [format_language(X) || X <- language:list_names()], 28 | {jsx:encode(Names), Req, State}. 29 | 30 | format_language(Name) -> 31 | BaseUrl = config:base_url(), 32 | #{ 33 | name => Name, 34 | url => <> 35 | }. 36 | -------------------------------------------------------------------------------- /apps/glot/src/resources/root_resource.erl: -------------------------------------------------------------------------------- 1 | -module(root_resource). 2 | -export([ 3 | init/3, 4 | rest_init/2, 5 | allowed_methods/2, 6 | content_types_provided/2, 7 | root/2 8 | ]). 9 | 10 | init(_Transport, _Req, _Opts) -> 11 | {upgrade, protocol, cowboy_rest}. 12 | 13 | rest_init(Req, []) -> 14 | {ok, Req, []}. 15 | 16 | allowed_methods(Req, State) -> 17 | {[<<"GET">>], Req, State}. 18 | 19 | content_types_provided(Req, State) -> 20 | Handlers = [ 21 | {{<<"application">>, <<"json">>, '*'}, root} 22 | ], 23 | {Handlers, Req, State}. 24 | 25 | root(Req, State) -> 26 | BaseUrl = config:base_url(), 27 | Data = #{ 28 | description => config:glot_description(), 29 | version => config:glot_version(), 30 | urls => #{ 31 | admin => <>, 32 | images => <>, 33 | languages => <> 34 | } 35 | }, 36 | {jsx:encode(Data), Req, State}. 37 | 38 | -------------------------------------------------------------------------------- /apps/glot/src/util.erl: -------------------------------------------------------------------------------- 1 | -module(util). 2 | 3 | -export([ 4 | sha1/1, 5 | pid_to_binary/1 6 | ]). 7 | 8 | sha1(Data) -> 9 | hexstring(crypto:hash(sha, Data)). 10 | 11 | hexstring(<>) -> 12 | iolist_to_binary(io_lib:format("~32.16.0b", [X])); 13 | hexstring(<>) -> 14 | iolist_to_binary(io_lib:format("~40.16.0b", [X])); 15 | hexstring(<>) -> 16 | iolist_to_binary(io_lib:format("~56.16.0b", [X])); 17 | hexstring(<>) -> 18 | iolist_to_binary(io_lib:format("~64.16.0b", [X])); 19 | hexstring(<>) -> 20 | iolist_to_binary(io_lib:format("~96.16.0b", [X])); 21 | hexstring(<>) -> 22 | iolist_to_binary(io_lib:format("~128.16.0b", [X])). 23 | 24 | pid_to_binary(Pid) -> 25 | list_to_binary(pid_to_list(Pid)). 26 | -------------------------------------------------------------------------------- /config/relx.config: -------------------------------------------------------------------------------- 1 | {paths, ["apps", "deps"]}. 2 | 3 | {include_erts, true}. 4 | {include_src, false}. 5 | 6 | {release, {glot, "1.2.1"}, [glot]}. 7 | 8 | {extended_start_script, true}. 9 | {vm_args, "config/vm.args"}. 10 | -------------------------------------------------------------------------------- /config/vm.args: -------------------------------------------------------------------------------- 1 | -name glot-run@127.0.0.1 2 | -setcookie glot 3 | -smp auto 4 | -------------------------------------------------------------------------------- /devel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export API_ENVIRONMENT="development" 3 | export API_HTTP_LISTEN_IP="0.0.0.0" 4 | export API_HTTP_LISTEN_PORT="8090" 5 | export DATA_PATH="/tmp/glot/data/" 6 | export LOG_PATH="/tmp/glot/log/" 7 | export BASE_URL="http://localhost:8090" 8 | export ADMIN_TOKEN="clumeterin" 9 | export DOCKER_API_URL="http://10.0.0.46:2375" 10 | export DOCKER_RUN_TIMEOUT="15" 11 | export MAX_OUTPUT_SIZE="500000" 12 | 13 | cd `dirname $0` 14 | rebar3 shell --apps glot 15 | -------------------------------------------------------------------------------- /dialyzer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build plt 4 | # dialyzer --build_plt --apps erts kernel stdlib crypto sasl 5 | # dialyzer --add_to_plt --apps ssl reltool 6 | 7 | dialyzer -pa deps/hackney/ebin -r apps/*/src --src 8 | -------------------------------------------------------------------------------- /docker-pull.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export DOCKER_HOST="tcp://127.0.0.1:80" 6 | export ADMIN_TOKEN="some-secret" 7 | export RUN_HOST="localhost:8080" 8 | 9 | docker -H $DOCKER_HOST pull glot/python 10 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "python", "version": "latest", "image": "glot/python:latest"}' "http://$RUN_HOST/admin/languages" 11 | docker -H $DOCKER_HOST pull glot/python:2 12 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "python", "version": "2", "image": "glot/python:2"}' "http://$RUN_HOST/admin/languages" 13 | docker -H $DOCKER_HOST pull glot/clang 14 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "c", "version": "latest", "image": "glot/clang:latest"}' "http://$RUN_HOST/admin/languages" 15 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "cpp", "version": "latest", "image": "glot/clang:latest"}' "http://$RUN_HOST/admin/languages" 16 | docker -H $DOCKER_HOST pull glot/php 17 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "php", "version": "latest", "image": "glot/php:latest"}' "http://$RUN_HOST/admin/languages" 18 | docker -H $DOCKER_HOST pull glot/javascript 19 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "javascript", "version": "latest", "image": "glot/javascript:latest"}' "http://$RUN_HOST/admin/languages" 20 | docker -H $DOCKER_HOST pull glot/javascript:es6 21 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "javascript", "version": "es6", "image": "glot/javascript:es6"}' "http://$RUN_HOST/admin/languages" 22 | docker -H $DOCKER_HOST pull glot/java 23 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "java", "version": "latest", "image": "glot/java:latest"}' "http://$RUN_HOST/admin/languages" 24 | docker -H $DOCKER_HOST pull glot/golang 25 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "go", "version": "latest", "image": "glot/golang:latest"}' "http://$RUN_HOST/admin/languages" 26 | docker -H $DOCKER_HOST pull glot/bash 27 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "bash", "version": "latest", "image": "glot/bash:latest"}' "http://$RUN_HOST/admin/languages" 28 | docker -H $DOCKER_HOST pull glot/ruby 29 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "ruby", "version": "latest", "image": "glot/ruby:latest"}' "http://$RUN_HOST/admin/languages" 30 | docker -H $DOCKER_HOST pull glot/rust 31 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "rust", "version": "latest", "image": "glot/rust:latest"}' "http://$RUN_HOST/admin/languages" 32 | docker -H $DOCKER_HOST pull glot/mono 33 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "csharp", "version": "latest", "image": "glot/mono:latest"}' "http://$RUN_HOST/admin/languages" 34 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "fsharp", "version": "latest", "image": "glot/mono:latest"}' "http://$RUN_HOST/admin/languages" 35 | docker -H $DOCKER_HOST pull glot/haskell 36 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "haskell", "version": "latest", "image": "glot/haskell:latest"}' "http://$RUN_HOST/admin/languages" 37 | docker -H $DOCKER_HOST pull glot/coffeescript 38 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "coffeescript", "version": "latest", "image": "glot/coffeescript:latest"}' "http://$RUN_HOST/admin/languages" 39 | docker -H $DOCKER_HOST pull glot/elixir 40 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "elixir", "version": "latest", "image": "glot/elixir:latest"}' "http://$RUN_HOST/admin/languages" 41 | docker -H $DOCKER_HOST pull glot/erlang 42 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "erlang", "version": "latest", "image": "glot/erlang:latest"}' "http://$RUN_HOST/admin/languages" 43 | docker -H $DOCKER_HOST pull glot/assembly 44 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "assembly", "version": "latest", "image": "glot/assembly:latest"}' "http://$RUN_HOST/admin/languages" 45 | docker -H $DOCKER_HOST pull glot/clojure 46 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "clojure", "version": "latest", "image": "glot/clojure:latest"}' "http://$RUN_HOST/admin/languages" 47 | docker -H $DOCKER_HOST pull glot/lua 48 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "lua", "version": "latest", "image": "glot/lua:latest"}' "http://$RUN_HOST/admin/languages" 49 | docker -H $DOCKER_HOST pull glot/scala 50 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "scala", "version": "latest", "image": "glot/scala:latest"}' "http://$RUN_HOST/admin/languages" 51 | docker -H $DOCKER_HOST pull glot/perl 52 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "perl", "version": "latest", "image": "glot/perl:latest"}' "http://$RUN_HOST/admin/languages" 53 | docker -H $DOCKER_HOST pull glot/nim 54 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "nim", "version": "latest", "image": "glot/nim:latest"}' "http://$RUN_HOST/admin/languages" 55 | docker -H $DOCKER_HOST pull glot/dlang 56 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "d", "version": "latest", "image": "glot/dlang:latest"}' "http://$RUN_HOST/admin/languages" 57 | docker -H $DOCKER_HOST pull glot/idris 58 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "idris", "version": "latest", "image": "glot/idris:latest"}' "http://$RUN_HOST/admin/languages" 59 | docker -H $DOCKER_HOST pull glot/ocaml 60 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "ocaml", "version": "latest", "image": "glot/ocaml:latest"}' "http://$RUN_HOST/admin/languages" 61 | docker -H $DOCKER_HOST pull glot/elm 62 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "elm", "version": "latest", "image": "glot/elm:latest"}' "http://$RUN_HOST/admin/languages" 63 | docker -H $DOCKER_HOST pull glot/julia 64 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "julia", "version": "latest", "image": "glot/julia:latest"}' "http://$RUN_HOST/admin/languages" 65 | docker -H $DOCKER_HOST pull glot/swift 66 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "swift", "version": "latest", "image": "glot/swift:latest"}' "http://$RUN_HOST/admin/languages" 67 | docker -H $DOCKER_HOST pull glot/perl6 68 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "perl6", "version": "latest", "image": "glot/perl6:latest"}' "http://$RUN_HOST/admin/languages" 69 | docker -H $DOCKER_HOST pull glot/ats 70 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "ats", "version": "latest", "image": "glot/ats:latest"}' "http://$RUN_HOST/admin/languages" 71 | docker -H $DOCKER_HOST pull glot/groovy 72 | curl -X PUT -H "Authorization: Token $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"name": "groovy", "version": "latest", "image": "glot/groovy:latest"}' "http://$RUN_HOST/admin/languages" 73 | -------------------------------------------------------------------------------- /docker_server_config.md: -------------------------------------------------------------------------------- 1 | # Prepare docker for running glot/$language images 2 | ##### Distro: Ubuntu 17.10 3 | ##### Docker: 17.12.0-ce 4 | 5 | 6 | ## Install docker-ce stable 7 | [https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-repository) 8 | 9 | 10 | ## Configure docker 11 | 12 | Configure docker to listen on tcp port 2375 13 | 14 | Create file `/etc/systemd/system/docker.service.d/override.conf` with content: 15 | 16 | ``` 17 | [Service] 18 | ExecStart= 19 | ExecStart=/usr/bin/dockerd 20 | ``` 21 | 22 | Create file `/etc/docker/daemon.json` with content: 23 | 24 | ``` 25 | { 26 | "ip-forward": false, 27 | "iptables": false, 28 | "ipv6": false, 29 | "ip-masq": false, 30 | "hosts": ["fd://", "tcp://127.0.0.1:2375"] 31 | } 32 | ``` 33 | 34 | Reload config and restart docker 35 | 36 | ``` 37 | systemctl daemon-reload 38 | systemctl restart docker.service 39 | ``` 40 | 41 | ## Install haproxy 42 | 43 | Haproxy is used to expose docker on port 80 and also to restrict access from specific ip's. 44 | 45 | ``` 46 | apt-get install haproxy 47 | ``` 48 | 49 | 50 | ## Configure haproxy 51 | 52 | Append configuration to end of `/etc/haproxy/haproxy.cfg` 53 | 54 | ``` 55 | frontend docker-frontend 56 | bind 0.0.0.0:80 57 | acl network_allowed src 10.20.30.40 58 | tcp-request connection reject if !network_allowed 59 | default_backend docker-backend 60 | 61 | backend docker-backend 62 | balance leastconn 63 | server docker localhost:2375 64 | ``` 65 | Restart haproxy 66 | 67 | ``` 68 | systemctl restart haproxy.service 69 | ``` 70 | 71 | Pull glot language images 72 | 73 | ``` 74 | docker pull glot/bash:latest 75 | … 76 | ``` 77 | 78 | ## Configure glot-run 79 | 80 | ``` 81 | … 82 | # address to server running docker+haproxy 83 | DOCKER_API_URL="http://1.2.3.4" 84 | … 85 | ``` 86 | 87 | Test run bash code 88 | 89 | ``` 90 | curl -4 -sv -H "Authorization: Token some-token" -H 'Content-type: application/json' -X POST -d '{"files": [{"name": "main.sh", "content": "echo \"hello\"\n"}]}' localhost:8090/languages/bash/latest 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /make_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rebar3 compile 5 | rebar3 release -c config/relx.config 6 | root_path=$(pwd) 7 | 8 | ( 9 | cd _build/default/rel 10 | tar -czf ${root_path}/glot-run.tar.gz glot 11 | ) 12 | -------------------------------------------------------------------------------- /make_release_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script requires the following docker image to exist 4 | # 5 | # FROM erlang:latest 6 | # MAINTAINER Petter Rasmussen "petter.rasmussen@gmail.com" 7 | # 8 | # RUN mkdir /build 9 | # VOLUME ["/build"] 10 | # WORKDIR /build 11 | # CMD "/build/make_release.sh" 12 | 13 | docker run \ 14 | --volume $(pwd):/build \ 15 | --rm \ 16 | prasmussen/erlang-build:latest 17 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {lib_dirs, ["deps", "apps"]}. 2 | {sub_dirs, ["apps/*"]}. 3 | 4 | {erl_opts, [ 5 | {i, "apps"}, 6 | {i, "deps"}, 7 | {parse_transform, lager_transform} 8 | ]}. 9 | 10 | {deps, [ 11 | {lager, ".*", {git, "git://github.com/basho/lager", {tag, "2.2.2"}}}, 12 | {jsx, "2.*", {git, "git://github.com/talentdeficit/jsx", {tag, "v2.8.0"}}}, 13 | {cowboy, ".*", {git, "git://github.com/ninenines/cowboy", {tag, "1.0.4"}}}, 14 | {hackney, ".*", {git, "git://github.com/benoitc/hackney.git", {tag, "1.6.0"}}}, 15 | {iso8601, ".*", {git, "git://github.com/erlsci/iso8601.git", {tag, "1.2"}}}, 16 | {uuid, ".*", {git, "git://github.com/okeuday/uuid.git", {tag, "v1.5.1.1"}}}, 17 | {sync, ".*", {git, "git://github.com/rustyio/sync.git", "HEAD"}} 18 | ]}. 19 | -------------------------------------------------------------------------------- /set_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vsn=$1 4 | 5 | sed -i '' "s/{vsn, .*/{vsn, \"$vsn\"},/" apps/glot/src/glot.app.src 6 | sed -i '' "s/{default_release, glot.*/{default_release, glot, \"$vsn\"}./" config/relx.config 7 | sed -i '' "s/{release, {glot.*/{release, {glot, \"$vsn\"}, [glot]}./" config/relx.config 8 | --------------------------------------------------------------------------------