mirror of
https://github.com/avatao-content/test-tutorial-framework
synced 2024-11-15 05:47:17 +00:00
252 lines
12 KiB
Markdown
252 lines
12 KiB
Markdown
# tutorial-framework test
|
||
|
||
This is an example playground project built via TFW.
|
||
It is a good starting point to build your own challenges from and will host automated tests in the future.
|
||
|
||
It also gives home to several useful scripts in the `hack` folder to speed up development.
|
||
|
||
## Getting started
|
||
|
||
TFW consists of 3 repositories:
|
||
- [baseimage-tutorial-framework](https://github.com/avatao-content/baseimage-tutorial-framework) – Docker baseimage
|
||
- [frontend-tutorial-framework](https://github.com/avatao-content/frontend-tutorial-framework) – Angular frontend
|
||
- [test-tutorial-framework](https://github.com/avatao-content/test-tutorial-framework) (this repo)
|
||
|
||
See the documentation of each in their `README.md` files. To publish your challenge on the Avatao platform please check out the details on [docs.avatao-challenge.com](https://docs.avatao-challenge.com/publish-challenge).
|
||
|
||
Note that before getting started with TFW you should already know how to create simpler Avatao challenges.
|
||
To learn about Avatao challenge creation in general please consult the [challenge-toolbox](https://github.com/avatao-content/challenge-toolbox) repository.
|
||
|
||
To learn the stuff you need to know about TFW in order to get started you should consult the [baseimage-tutorial-framework](https://github.com/avatao-content/baseimage-tutorial-framework) repo first.
|
||
|
||
Getting started with creating challenges using the framework – *setting up a development environment, building, running and such* – is documented here.
|
||
|
||
## Setting up a development environment
|
||
|
||
Dependencies:
|
||
- bash
|
||
- git
|
||
- Docker
|
||
- yarn
|
||
- Angular CLI
|
||
- GNU coreutils
|
||
|
||
Just copy and paste the following command in a terminal:
|
||
|
||
`bash -c "$(curl -fsSL https://git.io/vxBfj)"`
|
||
|
||
Please do not hesitate to contact us with error logs included should this command fail to complete.
|
||
Note that your SSH public key must be added to your GitHub user for this to work, or you mush select HTTPS remotes when prompted.
|
||
|
||
This will set up a dev environment based on [test-tutorial-framework](https://github.com/avatao-content/test-tutorial-framework) just for you:
|
||
- it builds the latest release of the framework Docker baseimage locally
|
||
- it pins `solvable/Dockerfile` to use the this image
|
||
- it includes the latest frontend in `solvable/frontend` with dependencies installed
|
||
|
||
By default your IDE will fail to autocomplete code and will complain about missing dependencies.
|
||
To fix this you should install the `tfw` pip package in your dev virtualenv:
|
||
|
||
- `pip install git+ssh://git@github.com/avatao-content/baseimage-tutorial-framework.git` (SSH)
|
||
- `pip install git+https://github.com/avatao-content/baseimage-tutorial-framework.git` (HTTPS)
|
||
|
||
## Building & running
|
||
|
||
### Automated
|
||
|
||
Our magical bash script `hack/tfw.sh` can handle everything for you. Just run it without any arguments to see usage information.
|
||
|
||
It is advisable to run the frontend locally while developing to avoid really looooong build times. The `hack/tfw.sh` script handles this for you automagically.
|
||
|
||
### Challenge-toolbox
|
||
|
||
You can also use our [toolbox](https://github.com/avatao-content/challenge-toolbox) to build and run TFW based challenges, just like the regular ones.
|
||
Note that this will always create a production build with the frontend included.
|
||
|
||
### Doing it manually
|
||
|
||
In case you **must** *really* do it then you can build & run manually.
|
||
Note that this is relatively painful and you should use the `hack/tfw.sh` script when possible.
|
||
|
||
Building without frontend – execute from project root:
|
||
|
||
`docker build -t test-tutorial-framework -f solvable/Dockerfile --build-arg BUILD_CONTEXT=solvable --build-arg NOFRONTEND=1 .`
|
||
|
||
This will create a Docker image without the frontend, which you can run locally. For procudtion builds exclude the argument `--build-arg NOFRONTEND=1` to include a frontend instance.
|
||
|
||
Execute the following command to run the image:
|
||
|
||
`docker run --rm -p 8888:8888 -e AVATAO_SECRET=secret test-tutorial-framework`
|
||
|
||
In case of a build without frontend (built with `--build-arg NOFRONTEND=1` included) you will need to run `yarn start` from the `solvable/frontend` directory as well.
|
||
This will serve the frontend locally on `http://localhost:4200` and take care of proxying.
|
||
|
||
If you've created a production build (without `--build-arg NOFRONTEND=1`) you don't have to run the frontend locally and you can access the challenge on `http://localhost:8888`.
|
||
|
||
### Building the TFW baseimage without test-tutorial-framework
|
||
|
||
You might need to build our baseimage separately in case you've cloned an existing challenge depending on a specific version.
|
||
|
||
To do this simply issue `BASEIMAGE_ONLY=version bash -c "$(curl -fsSL https://git.io/vxBfj)"`, where `version` is a tag or commit of the [baseimage-tutorial-framework](https://github.com/avatao-content/baseimage-tutorial-framework) repository.
|
||
|
||
## Getting our hands dirty
|
||
|
||
The repository of a tutorial-framework based challenge is quite similar to a regular challenge.
|
||
The project root should look something like this:
|
||
|
||
```
|
||
your_repo
|
||
├── solvable
|
||
│ └── [TFW based Docker image]
|
||
├── controller
|
||
│ └── [solution checking]
|
||
├── metadata
|
||
│ └── [challenge descriptions, writeups, etc.]
|
||
└── config.yml
|
||
```
|
||
|
||
The only notable difference is that the `solvable` Docker image is a child of our baseimage: `solvable/Dockerfile` begins with `FROM eu.gcr.io/avatao-challengestore/tutorial-framework`.
|
||
|
||
From now on we are going to focus on the `solvable` image.
|
||
|
||
### Basics of a TFW based challenge
|
||
|
||
Let us take a closer look on `solvable`:
|
||
|
||
```
|
||
solvable
|
||
├── Dockerfile
|
||
├── nginx webserver configurations
|
||
├── supervisor process manager (init replacement)
|
||
├── frontend clone of the frontend-tutorial-framework repo with dependencies installed
|
||
└── src example source code
|
||
```
|
||
|
||
Note that our baseimage *requires* the `nginx`, `supervisor` and `frontend` folders to be in these **exact** locations and to be used as described below.
|
||
This is a contract your image **must** comply.
|
||
|
||
The `src` directory contains a simple example of using TFW
|
||
|
||
### nginx
|
||
|
||
All TFW based challenges expose a single port defined in the `TFW_PUBLIC_PORT` envvar which is set to `8888` by default.
|
||
This means that in order to listen on more than a single port we must use a reverse proxy.
|
||
|
||
Any `.conf` files in `solvable/nginx/` will be automatically included in the nginx configuration.
|
||
In case you want to serve a website or service you must proxy it through `TFW_PUBLIC_PORT`.
|
||
This is really easy: just create a config file in `solvable/nginx/` similar to this one:
|
||
```
|
||
location /yoururl {
|
||
proxy_pass http://127.0.0.1:3333;
|
||
}
|
||
```
|
||
After this you can access the service running on port `3333` at `http://localhost:8888/yoururl`
|
||
|
||
It is very important to understand that from now on your application must behave well behind a reverse proxy.
|
||
What this means is all `href`s must point the proxied paths (e.g. links should refer to `/yoururl/register` instead of `/register`) on your HTML pages.
|
||
|
||
You can learn about configuring nginx in [this](https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts) handy little tutorial.
|
||
|
||
### supervisor
|
||
|
||
In most Docker conainers there is a single process running (it gets `PID 1`).
|
||
When working with TFW you can run as many processes as you want to by using supervisord.
|
||
|
||
Any `.conf` files in the `solvable/supervisor/` directory will be included in the supervisor configuration.
|
||
The programs, you define this way, are easy to manage (starting/stopping/restarting) using the `supervisorctl` command line tool or our built-in event handler.
|
||
You can even configure your processes to start with the container by including `autostart=true` in your configuration file.
|
||
|
||
To run your own webservice for instance you need to create a config file in `solvable/supervisor/` similar to this one:
|
||
|
||
```
|
||
[program:yourprogram]
|
||
user=user
|
||
directory=/home/user/example/
|
||
command=python3 server.py
|
||
autostart=true
|
||
```
|
||
|
||
This starts the `/home/user/example/server.py` script using `python3` after your container entered the running state (because of `autostart=true`, supervisor does not start programs by default).
|
||
|
||
You can learn more about configuring supervisor [here](http://supervisord.org/configuration.html).
|
||
|
||
### frontend
|
||
|
||
This is a clone of the `frontend-tutorial-framework` repository with dependencies installed in `solvable/frontend/node_modules`.
|
||
|
||
You can modify it to fit your needs, but this requires some Angular knowledge (not much at all).
|
||
|
||
If all you want to do is starting a simple web application and to send some messages you can mostly skip the Angluar knowledge bit.
|
||
Refer to the example in this repo.
|
||
|
||
### src
|
||
|
||
This folder contains the source code of a server running TFW and an other server running our event handlers.
|
||
Note that this is not a part of the framework by any means, these are just simple examples.
|
||
|
||
```
|
||
solvable/src
|
||
├── event_handler_main.py event handlers implemented in python
|
||
├── test_fsm.py example FSM in python
|
||
└── test_fsm.yml example FSM in yaml
|
||
```
|
||
|
||
`event_handler_main.py` contains example usage of our pre-defined event handlers written in Python3.
|
||
As you can see they run in a separate process (set up in `solvable/supervisor/event_handler_main.conf`).
|
||
These event handlers could be implemented in any language that has ZMQ bindings.
|
||
|
||
Note that you don't have to use all our event handlers.
|
||
Should you want to avoid using a feature, you can just delete the appropriate event handler from `event_handler_main.py`.
|
||
|
||
`test_fsm.yml` and `test_fsm.py` are the implementations of the same FSM in YAML and Python to provide you examples of creating your own machine.
|
||
|
||
It is genarally a good idea to separate these files from the rest of the stuff in `solvable`, so it is a good practice to create an `src` directory.
|
||
|
||
### FSM
|
||
|
||
A good state machine is the backbone of a good TFW challenge.
|
||
|
||
There are two ways to define a state machine:
|
||
- Using a YAML configuration file
|
||
- Implementing it in Python by hand
|
||
|
||
The first option allows you to handle FSM callbacks and custom logic in any programming language (not just Python) and is generally really easy to work with (you can execute arbitrary shell commands on events).
|
||
You should choose this method unless you have good reason not to.
|
||
This involves creating your YAML file (see `test_fsm.yml` for an example) and parsing it using our `YamlFSM` class (see `event_handler_main.py` for an example).
|
||
|
||
The second option allows you to implement your FSM in Python, using the transitions library.
|
||
To do this just subclass our `FSMBase` class or use our `LinearFSM` class for simple machines (see `test_fsm.py` for an example).
|
||
|
||
In your FSM you can define callbacks for states and transitions.
|
||
State callbacks:
|
||
- `on_enter`
|
||
- `on_exit`
|
||
Transition callbacks:
|
||
- `before`
|
||
- `after`
|
||
|
||
In your YAML file you can use these in the state and transition objects as keys, then add a shell command to run as a value (again, see `test_fsm.yml` for examples).
|
||
|
||
It is also possible to add preconditions to transitions.
|
||
This is done by adding a `predicates` key with a list of shell commands to run.
|
||
If you do this, the transition will only succeed if the return code of all predicates was `0` (as per unix convention for success).
|
||
|
||
## Baby steps
|
||
|
||
When creating your own challenge the process should be the following:
|
||
1. Use our install script to bootstrap your dev environment
|
||
2. Create an FSM that describes your challenge
|
||
- An example is in `solvable/src/test_fsm.yml`
|
||
- The same FSM in python is in `solvable/src/test_fsm.py`
|
||
3. Create a `TFWServer` instance and set it up to run:
|
||
- Create a server app: `solvable/src/tfw_server.py`
|
||
- Set it up to run: `solvable/supervisor/tfw_server.conf`
|
||
4. Create event handlers connecting to the `TFWServer` handling events you want to process:
|
||
- Create an event handler server: `solvable/src/event_handler_main.py`
|
||
- Set it up to run: `solvable/supervisor/event_handler_main.conf`
|
||
5. Modify the frontend in `solvable/frontend` to fit your challenge
|
||
- This usually involves using our pre-made components
|
||
- And perhaps doing some of your own stuff, like:
|
||
- Sending messages then handling these in event handlers written in step 4
|
||
- Sending triggers to step the FSM
|
||
- Including images of cats – http://thecatapi.com
|