소스 검색

Improving developer experience

Kirk Trombley 6 년 전
부모
커밋
5cddd83d15
6개의 변경된 파일275개의 추가작업 그리고 5개의 파일을 삭제
  1. 1 1
      Dockerfile
  2. 144 1
      README.md
  3. 3 3
      config/secrets.toml.template
  4. 35 0
      mkplugin.sh
  5. 89 0
      rollbot-docker.sh
  6. 3 0
      src/rollbot.py

+ 1 - 1
Dockerfile

@@ -7,4 +7,4 @@ RUN pip install -r requirements.txt
 ADD config/config.toml /rollbot
 ADD config/secrets.toml /rollbot
 ADD src/ /rollbot
-CMD ["gunicorn", "app:app", "--bind", "unix:/tmp/rollbot.sock"]
+CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:6070"]

+ 144 - 1
README.md

@@ -13,4 +13,147 @@
 `cd` into `src/` and run
 `ROLLBOT_CFG_DIR=../config python3 app.py`
 
-Note this requires at least Python 3.7.1
+Note this requires at least Python 3.7.1
+
+## Development/Contributing
+
+
+### First Time Set-Up
+If you are developing this project, start by cloning this repository and creating a new branch,
+replacing `my-awesome-branch` with your chosen branch name in the following
+
+```bash
+git clone ssh://git@kirkleon.ddns.net:10022/kirkleon/rollbot3.git
+cd rollbot3/
+git checkout -b feature/my-awesome-branch
+```
+
+Next, open GroupMe through your preferred application, and create a new chat containing just
+yourself. This will be your testing chat, where your local copy of rollbot will post messages.
+Future plans for the project include a simpler local testing environment, but integrating with
+GroupMe is an important testing step regardless, and so it is reasonable to do it now anyway.
+
+Next, create a `secrets.toml` file. Do **NOT** commit this file! It is by default added to `.gitignore`
+for you, but you should always take care you are not accidentally sharing this file.
+
+```bash
+cp config/secrets.toml.template config/secrets.toml
+```
+
+Open the new `secrets.toml` file in your preferred editor, and then navigate to the GroupMe
+[Bots](https://dev.groupme.com/bots) page. In the upper right, you should see a button that says
+`Access Token`. Click this, and copy the token into your `secrets.toml`, setting it as the value
+for `api-key`. Then, on the bots page, create a new bot. For this bot's group, select the new
+chat you made above. Name can be whatever you like, and you can leave the other fields blank.
+Click submit, and retrieve your `Bot ID` and `Group ID` from the bots page. Put these in your
+`secrets.toml` under the `bots` section, with your `Group ID` serving as the key and the `Bot ID`
+serving as the value (which must be in quotes).
+
+For example, if your API key is `123`, your bot ID is `456`, and your group ID is `789`, your
+`secrets.toml` needs to start with the following
+
+```toml
+api_key = "123"
+
+[bots]
+789 = "456"
+```
+
+That's it for secrets! Save the file and move on to deciding if you want to do your local development
+with or without `docker`.
+
+### Developing w/o Docker
+
+If you have a Python 3.7.1 environment with `pip` available, you can install dependencies as follows
+
+```bash
+pip install -r requirements.txt
+```
+
+If your plugin or extension adds new dependencies, remember to include them in this `requirements.txt`.
+
+Then, move to the `src/` directory and use the above command to start the local `Flask` server on
+port `6070`.
+
+```bash
+`ROLLBOT_CFG_DIR=../config python3 app.py`
+```
+
+Use `Ctrl-C` to kill this server. Your development loop will probably look something like, modify
+your plugin, start the server, test it, kill the server, repeat.
+
+### Developing w/ Docker
+
+If you do not have Python 3.7.1 (or later) available, or want to develop using docker locally, you
+can make use of the `rollbot-docker.sh` script. You will need to run this as `root` (preferably with
+`sudo`), or as some user capable of using docker. If you are not running this script as `root`, and
+instead have a different local docker user, you will need to `export FORCE_NOROOT=*` before running
+the script.
+
+You can run `./rollbot-docker.sh help` to see a full list of options. The most useful are `run` and
+`clean`. To build the `rollbot3:latest` image and deploy it to a docker container called
+`rollbot3-instance` on your local machine, simply run
+
+```bash
+./rollbot-docker.sh run
+```
+
+This will also blow away any existing container called `rollbot3-instance` before building the new
+image and container. If you would just like to kill and remove existing containers with that name,
+without starting a new one, you can run
+
+```bash
+./rollbot-docker.sh clean
+```
+
+In general, your development loop will probably look something like, modify your plugin, run
+`./rollbot-docker.sh run`, and repeat. When you are all done, you can use `./rollbot-docker.sh clean`
+to remove the lingering container on your system.
+
+### Writing a Plugin
+
+The quickstart for creating a new plugin is to determine a name for the plugin, say, `my_cool_plugin`,
+and run
+
+```bash
+./mkplugin my_cool_plugin
+```
+
+This will end by printing something like
+
+```
+Action required: Insert the following line into the [plugins] section of config/config.toml to activate my_cool_plugin
+my_cool_plugin = [ "my_cool_plugin"]
+```
+
+Do as it says and open `config/config.toml` and add the printed line to the `[plugins]` map.
+
+This script will also generate a file at `src/plugins/my_cool_plugin.py` containing
+
+```python
+from command_system import as_plugin, RollbotResponse
+
+
+@as_plugin("my_cool_plugin")
+def my_cool_plugin(db, msg):
+    return RollbotResponse(msg, txt="My first plugin!")
+```
+
+This is the most basic possible rollbot plugin, which will simply respond with `My first plugin!`
+when it receives a message starting with `!my_cool_plugin`. You can modify the argument to `as_plugin`
+to change the plugin's command word, which must be unique within the application.
+
+Further docs to come! Good luck!
+
+### Contributing Your Plugin
+
+Once you are confident your plugin is functional, push your code to remote with the following, again
+replacing `my-awesome-branch` with your chosen branch name.
+
+```bash
+git push -u origin feature/my-awesome-branch
+```
+
+Then, open a [Pull Request](https://kirkleon.ddns.net/gogs/kirkleon/rollbot3/pulls), shoot me a message,
+and I'll take it from there!
+

+ 3 - 3
config/secrets.toml.template

@@ -1,14 +1,14 @@
 api_key = "YOUR-API-KEY"
 
+[bots]
+your_chat = "BOT-TOKEN"
+
 [auths]
 global = [ ] # List of Global Admin User IDs
 
     [auths.group]
     your_chat = [ ] # Per-chat Admin User IDs
 
-[bots]
-your_chat = "BOT-TOKEN"
-
 [teamspeak]
 pass = "your-teamspeak-serveradmin-password"
 

+ 35 - 0
mkplugin.sh

@@ -0,0 +1,35 @@
+#!/usr/bin/env sh
+
+if [ $# -lt 1 ]
+then
+    echo "usage: $0 plugin-name"
+    echo "  This command generates a file at src/plugins/plugin-name.py, containing the base of a rollbot plugin."
+    echo "  The plugin-name must be a valid python identifier, and ideally should only be composed of lowercase letters and underscores"
+    exit 1
+fi
+
+if ! [[ "$1" =~ ^[a-z][a-z_]*$ ]]
+then
+    echo "Plugin names should be composed entirely of lowercase letters and underscores"
+    exit 1
+fi
+
+PLUGIN_FILE="./src/plugins/$1.py"
+
+if [[ -f $PLUGIN_FILE ]]
+then
+    echo "Plugin module with this name already exists - pick a new module name. You can always bind it to a different command word later!"
+    exit 1
+fi
+
+echo "Populating $PLUGIN_FILE with basic plugin called $1"
+
+echo "from command_system import as_plugin, RollbotResponse" >> $PLUGIN_FILE
+echo "" >> $PLUGIN_FILE
+echo "" >> $PLUGIN_FILE
+echo "@as_plugin(\"$1\")" >> $PLUGIN_FILE
+echo "def $1(db, msg):" >> $PLUGIN_FILE
+echo "    return RollbotResponse(msg, txt=\"My first plugin!\")" >> $PLUGIN_FILE
+
+echo "Action required: Insert the following line into the [plugins] section of config/config.toml to activate $1"
+echo "$1 = [ \"$1\" ]"

+ 89 - 0
rollbot-docker.sh

@@ -0,0 +1,89 @@
+#!/usr/bin/env sh
+
+if [ $# -lt 1 ] || [ "$1" == "help" ] || [ "$1" == "h" ]
+then
+    echo "usage: $0 (help|status|clean|run|logs) [container-name]"
+    echo "  help   - print this message"
+    echo "  status - report whether a container named container-name can be found, and whether or not it is running"
+    echo "  clean  - kill and remove any existing containers matching container-name"
+    echo "  run    - kill and remove any existing containers matching container-name, build the current directory into a new image, and deploy a new container container-name"
+    echo "  logs   - tail the logs of the container named container-name, equivalent to docker logs -f container-name"
+    echo "  container-name defaults to rollbot3-instance"
+    echo "  Run as root to ensure docker can be used, or set FORCE_NOROOT=* to force the script to run anyway."
+    exit 1
+fi
+
+if [ "$EUID" -ne 0 ] && [ -z "$FORCE_NOROOT" ]
+then
+    echo "$0 must be run as root to use docker. Set FORCE_NOROOT=* to force the script to run anyway."
+    exit 1
+fi
+
+if [ $# -gt 1 ]
+then
+    CONTAINER_NAME=$2
+else
+    CONTAINER_NAME="rollbot3-instance"
+fi
+
+status_check() {
+    docker inspect -f '{{.State.Running}}' $CONTAINER_NAME 2> /dev/null
+}
+
+clean_container() {
+    STATUS=$(status_check)
+    if [ "$STATUS" = "true" ]
+    then
+        echo "Container $CONTAINER_NAME is running and will be stopped and removed."
+        docker kill $CONTAINER_NAME
+        docker rm $CONTAINER_NAME
+    elif [ "$STATUS" = "false" ]
+    then
+        echo "Container $CONTAINER_NAME is stopped and will be removed."
+        docker rm $CONTAINER_NAME
+    else
+        echo "No existing container $CONTAINER_NAME could be found, no cleanup will be performed."
+    fi
+}
+
+case $1 in
+    "l"|"logs")
+        STATUS=$(status_check)
+        if [ "$STATUS" = "true" ]
+        then
+            docker logs -f $CONTAINER_NAME
+        elif [ "$STATUS" = "false" ]
+        then
+            echo "Existing container $CONTAINER_NAME is stopped."
+        else
+            echo "No existing container $CONTAINER_NAME could be found."
+        fi
+    ;;
+    "r"|"run")
+        clean_container
+        echo "Building rollbot3 image as rollbot3:latest"
+        docker build -t rollbot3:latest .
+        echo "Executing new container $CONTAINER_NAME using rollbot3:latest"
+        docker run -p6070:6070 --name $CONTAINER_NAME -d rollbot3
+        echo "Rollbot endpoint accessible at http://localhost:6070/rollbot"
+    ;;
+    "c"|"clean")
+        clean_container
+    ;;
+    "s"|"status")
+        STATUS=$(status_check)
+        if [ "$STATUS" = "true" ]
+        then
+            echo "Existing container $CONTAINER_NAME is running."
+        elif [ "$STATUS" = "false" ]
+        then
+            echo "Existing container $CONTAINER_NAME is stopped."
+        else
+            echo "No existing container $CONTAINER_NAME could be found."
+        fi
+    ;;
+    *)
+        echo "Unknown option $1, must be one of help, status, clean, run, logs."
+        exit 1
+    ;;
+esac

+ 3 - 0
src/rollbot.py

@@ -15,6 +15,9 @@ class Rollbot:
             for class_name in classes:
                 plugin_class = getattr(plugin_module, class_name)
                 plugin_instance = plugin_class(logger)
+                if plugin_instance.command in self.commands:
+                    self.logger.error(f"Duplicate command word '{plugin_instance.command}'")
+                    raise ValueError(f"Duplicate command word '{plugin_instance.command}'")
                 self.commands[plugin_instance.command] = plugin_instance
         self.logger.info(f"Finished loading plugins, {len(self.commands)} commands found")
         self.logger.info("Initializing database tables")