Rocket 2
Rocket 2 is the official UBC Launch Pad Slack bot and team management platform.
Rocket 2 is a from-the-ground-up rewrite of the original Rocket, and it is a Slack bot that aims to be a ChatOps-style tool for team management across platforms like GitHub and Google Drive, with extensive configuration options so that it can be used by other organizations as well. Rocket 2 is used, built, and maintained with ❤️ by UBC Launch Pad, UBC’s student-run software engineering club.
Main features |
|
---|---|
💬 |
Unix-style command system in Slack - invoke commands with a simple |
🔗 |
Platform integrations - easily configure GitHub organization invites and teams, Google Drive permissions, and more |
🗂 |
Team directory - provide and manage member information such as emails and other accounts |
🔒 |
Permissions system - control access to Rocket functionality with a tiered set of permissions |
🔨 |
Hackable and extensible - an open codebase makes it easy to add commands, scheduled modules, and more! |
📦 Usage¶
Check out our command reference pages to get started interacting with Rocket, or take a look at how Rocket is used at UBC Launch Pad in the Launch Pad handbook.
To set up a Rocket instance for your organization, refer to the deployment and configuration documentation.
📚 Contributing¶
Any contribution (pull requests, feedback, bug reports, ideas, etc.) is welcome!
Please refer to our contribution guide for contribution guidelines as well as detailed guides to help you get started with Rocket 2’s codebase.
Contribution Guide¶
This document contains important details for anyone contributing to Rocket 2.
Issues¶
Creating an Issue¶
If you see a bug or have a feature request, please open an issue! That being said, make sure to do a quick search first - there may already be an issue that covers it.
When creating a new issue, please use the existing templates, and make sure the appropriate labels are attached to the issue. If you are going to work on an issue, please assign yourself to it, and unassign yourself if you stop working on it.
Task Triage and Planning¶
All newly created issues are automatically added to the Rocket 2 Planning project board. Issues start in the Needs triage column. From here, they are moved to either:
❄️ Icebox: deprioritized tasks are tracked here
🗂 Backlog: this means that we want to get around to this task at some point
From the Backlog, we start moving tasks into 🚀 Planned, which is typically around when discussions around design and potential implementation happens. When work begins in earnest, the issue should be moved to 🏃♂️ In progress, where it will stay until a pull request lands closing the issue, at which point it will automatically be moved to ✅ Done.
We do not use the planning project to track pull requests - instead, relevant pull requests should be attached to their respective issues.
Development¶
Please refer to the local development guide to get started with making changes to Rocket 2!
Pull Requests¶
Before You Open a Pull Request¶
All tests and style and docs checks pass (
scripts/build_check.sh
)The GitHub build passes (GitHub will build your commit when you push it)
Your code is presentable and you have not committed extra files (think your credentials, IDE config files, cached directories, build directories, etc.)
You’ve written unit tests for the changes you’ve made, and that they cover all the code you wrote (or effectively all, given the circumstances)
We use codecov to check
code coverage, but you can easily check the code coverage using the
scripts/build_check.sh
script. The coverage should be displayed after
the unit tests are run.
Submitting a Pull Request¶
We appreciate pull requests of any size or scope.
Please use a clear, descriptive title for your pull request and fill out the pull request template with as much detail as you can. In particular, all pull requests should be linked to one or more issues - if a relevant issue does not exist, please create one as described above.
Note that you may open a pull request at any point during your progress - if a pull request is being opened as a request for feedback and help rather than a request for review and merge, then please open the pull request as a draft pull request <https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests>.
All pull requests must be code reviewed before being merged. Currently the code is primarily owned by the rocket2 team at UBC Launch Pad.
All pull requests must pass our GitHub build before they can be merged. The GitHub build checks for:
Passing unit tests (via pytest)
Minimum code coverage of unit tests (via Codecov.io)
Code linting (via flake8)
PEP8 code style (via pycodestyle)
Correctly-formatted docstrings (via pydocstyle)
All of these checks are conveniently done using the
scripts/build_check.sh
as mentioned above.
Remember to add the label Ready for Review
.
After your pull request has been approved and the GitHub build passes,
it can be merged into master
. Please do so with an ordinary merge
commit, not a rebase or squash merge.
Updating an Outdated Pull Request¶
If changes have been merged between when you started work on your branch and when your pull request was approved, you will have to update your branch. The preferred way to do so is with a rebase.
Assuming you are on your working branch:
git pull origin master
git rebase master
If you have changed files that were also changed in the intervening
merge, git rebase
may report merge conflicts. If this happens, don’t
panic! Use git status
and git diff
to determine which files
conflict and where, use an editor to fix the conflicts, then stage the
formerly-conflicting files with git add FILE
. Finally, use
git rebase --continue
to apply the fix and continue rebasing. Note
that you may have to fix conflicts multiple times in a single rebase.
It is also a good idea to replace the label Ready for Review
with
Ready for Re-Review
for clarity.
Local Development Guide¶
So, you want to see some progress, preferably on Slack, and not just in the forms of unit testing? At this point, fear is actually a reasonable response. With this guide, you can be talking to your locally-hosted Slack bot in no time!
Warning: This only works smoothly with a Unix machine (macOS or Linux variants). Windows users may be in for more pain than expected.
0. Developer Installation¶
We use pipenv for dependency management.
git clone https://github.com/ubclaunchpad/rocket2.git
cd rocket2/
pip install pipenv
pipenv install --dev
pipenv
will manage a
virtualenv, so interacting
with the program or using the development tools has to be done through
pipenv, like so:
pipenv run pycodestyle .
This can get inconvenient, so you can instead create a shell that runs in the managed environment like so:
pipenv shell
and then commands like pycodestyle
and pytest
can be run like
normal.
Additionally, we use Github Actions as a CI system. To run the same
checks locally, we provide scripts/build_check.sh
; this can be run
with:
./scripts/build_check.sh
The above tests would be run with the assumption that other applications, such as a local instance of DynamoDB, is also running. To run tests that explicitly do not involve the running of any database, run pytest with the following arguments:
pytest -m "not db"
You can also install it as a pre-commit hook for git:
cd scripts/
make install
Note that testing alongside a real Slack workspace, DynamoDB, and so on requires quite a bit more setup. For a full guide to developer installation, see our local development guide.
Running DynamoDB Locally¶
Some tests assume the existence of a local DynamoDB database. These are primarily for automated testing, like on Github Actions CI, but if you would like to run them yourself or are developing new tests, you can run as follows:
wget https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.tar.gz
mkdir DynamoDB
tar -xvf dynamodb_local_latest.tar.gz --directory DynamoDB
# Configure AWS
scripts/setup_localaws.sh
# Run DynamoDB through Java
cd DynamoDB/
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
# Open a new terminal to continue interacting
For a more sandboxed approach, you can use Docker and docker-compose to spin up a local DynamoDB instance:
docker-compose -f sandbox.yml up
You can then point a Rocket instance at this DynamoDB database by
setting AWS_LOCAL=True
.
1: Set up a HTTPS Tunnel¶
Slack requires that all webhooks are passed through HTTPS. This is rather inconvenient if you just want to test while running on your local computer. There are several ways to get around this.
Ngrok¶
Ngrok is a forwarding service that hosts a public HTTPS URL that passes to your local computer. Sign up for ngrok and download it here.
After installing, run ngrok http 5000
to create an ngrok URL that
will be passed to your local port 5000. As long as you run Rocket on
port 5000 (see below), you can then access it through the HTTPS URL that
ngrok gives you. Note that it is very important to use the HTTPS URL,
not the HTTP URL.
Localtunnel¶
Note: Localtunnel has reliability issues, which include crashing Rocket 2 after issuing a single command sometimes (for no discernable reason). Tread with caution.
An alternative to Ngrok is localtunnel, which works similarly to Ngrok but allows you to use the same domain every time. For example:
$ lt --port 5000 --subdomain my-amazing-rocket2
your url is: https://my-amazing-rocket2.loca.lt
2: Create a Slack Workspace¶
For testing, it’s useful to have your own Slack workspace set up. If you do not already have one, go here to create one, and follow the steps to set it up.
3: Create a Slack App¶
Follow the link here to create a new Slack app - you can name it whatever you like - and install it to the appropriate workspace.
3.1: Add a Bot Token¶
In “OAuth and Permissions”, select the Bot Token Scopes described in the Slack configuration docs.
3.2: Install Slack App¶
In “Install your app to your workspace,” click the button to install to your workspace. This will take you to a permissions page for the workspace - make sure this is for the correct workspace, and allow the app to connect.
Once this is done, you will be provided with an API token.
3.3: Determine Credentials¶
Make note of the app’s signing secret, found in Settings -> Basic Information -> App Credentials, and the bot user OAuth access token, found in Features -> OAuth & Permissions -> Tokens for Your Workspace. These will be needed for the configuration step later.
4: Gain Access to AWS¶
Rocket makes use of AWS DynamoDB as its database. There is also an optional logging component that leverages AWS CloudWatch.
Using Real AWS¶
If you do not already have access to DynamoDB and CloudWatch, you can
use it as part of the free tier of AWS. Create an AWS account for
yourself, then go to the IAM service and create a new user. The user
name doesn’t particularly matter (though rocket2-dev-$NAME
is
recommended), but make sure you check “programmatic access.” In
permissions, go to “Attach existing permissions directly” and add the
following policies:
AmazonDynamoDBFullAccess
CloudWatchLogsFullAccess
CloudWatchPutMetricData
You will have to create CloudWatchPutMetricData
. You can do this by going
to IAM -> Policies -> Create Policy. Choose service CloudWatch
. Choose
action PutMetricData
. Everything else should be set correctly - you can
create the policy with that.
As you may have noticed, we not only want to use DynamoDB, but also CloudWatch. We send our logs to CloudWatch for easier storage and querying. We also use it to track metrics.
Finally, copy the provided access key ID and secret access key after creating the new user.
Using Local AWS¶
Alternatively, just set up DynamoDB
locally (the Docker-based setup
is probably the easiest) and set AWS_LOCAL=True
.
CloudWatch integration is not currently supported in this manner.
5: Set up a GitHub App and organization¶
Create a Rocket 2 Github under an appropriate testing organization. Make sure to install the GitHub App to the organization in addition to registering it. All this can be done from a GitHub organization’s Settings > GitHub Apps page.
In the GitHub app’s settings, go to “Private keys” and click “Generate a
new private key”. This will generate and allow you to download a new
secret key for Rocket 2. Save this to the credentials/
directory as
gh_signing_key.pem
- it should already be in the PEM file format,
bracketed by:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Authenticating Rocket 2 as a GitHub App and obtaining an access token for the GitHub API should be automated, once the signing key is available. Refer to the GitHub key configuration docs for the required permissions.
After doing this, remember to put your tunneled HTTPS URL with
/webhook
appended at the end into the “Webhook URL” box. Refer to
the GitHub webhook configuration
docs for the required subscriptions.
6: Set Up Configuration¶
Our repo already contains sample-env
, the main environmental
configuration file for the entire app, as well as the credentials/
directory, where you will put credential files like the GitHub app
private key.
Please read the configuration docs for more details.
7: Build and Run Rocket 2¶
This section assumes you already have installed Docker. Assuming you are in the directory containing the Dockerfile, all you need to do to build and run is the following two commands (run from the root of your project directory):
scripts/docker_build.sh
scripts/docker_run_local.sh --env-file .env
Optionally, for local DynamoDB:
scripts/docker_run_local.sh --env-file .env --network="host"
The option –env-file lets you pass in your configuration options.
For the curious, you can take a look at the contents of the referenced
scripts above. Note that the options passed to -p
in docker run
tell Docker what port to run Rocket on. 0.0.0.0
is the IP address
(in this case, localhost), the first 5000
is the port exposed inside
the container, and the second 5000
is the port exposed outside the
container. The port exposed outside the container can be changed (for
instance, if port 5000 is already in use in your local development
environment), but in that case ensure that your tunnel is running on the
same port.
6.1: [Optional] Running without Docker¶
We highly recommend building and running on Docker, but building every
time you make a tiny change can be inconvenient. If you would like to
run without building a new Docker image every time, you can do so with
pipenv run launch
. This is in fact the same command Docker runs, but
if you run outside Docker, you may run into errors due to unexpected
changes in your local development environment.
7: Configure Slack App Features¶
In addition to a bot user, there are a couple other features that need to be enabled in the Slack app once the local instance of Rocket is running.
7.1: Add Event Subscriptions¶
In “Add features and functionality”, add event subscriptions. In
particular, under Request URL, submit the ngrok HTTPS URL with
/slack/events
appended to the end. Note that ngrok will generate a
new HTTPS URL every time it runs, so you will have to repeat this step
every time you launch ngrok. You will then have to enable workspace
and/or bot events that we want Rocket to listen for, like the
team_join
workspace event - ask the team for the most up-to-date
list of these.
7.2: Add Slash Command¶
In “Add features and functionality”, add a slash command. In particular,
under Request URL, submit the ngrok HTTPS URL with /slack/commands
appended to the end. For the actual command, anything will work, though
the final app will use /rocket
. Make sure you tick the box marked
“Escape channels, users, and links sent to your app”, or else none of
the @ signs will work properly!
8: Testing¶
This is the final and most important part: testing if it actually works
or not. Go to your Slack workspace and add Rocket (or whatever you named
your Slack bot) to the channel if you have yet to do so (just type
@<bot name>
and Slack will ask if you want to invite the bot into
the channel).
To test if Rocket is running, type the command:
/rocket user help
If you see a list of options, Rocket is working!
8.1: Setting Up Admin Permissions¶
We currently haven’t finished the command to add a user to the database or make them an admin, so we have to do it manually.
First, determine your Slack ID by reading the logs. The logs are formatted like so:
{slackid_making_the_command}:{command_itself}
The Slack IDs of other users will appear when you type @
followed by
whatever the user’s handle is. Slack automatically converts that handle
into an ID.
Then, you have an option of either using the AWS command-line interface or using the AWS web interface.
You should already have the command line interface installed via pipenv.
If not, run the command pipenv install --dev
. Note that to run
commands, you will either have to go into the pipenv environment (with
pipenv shell
) or prefix every command with pipenv run
. Here is
the command to create a user with a:
# The following command is split into multiple lines because it is long. Make
# sure that the actal command isn't split into multiple lines because it may
# complicate things.
aws dynamodb put-item --table-name USERS_TABLE\
--item '{"slack_id":{"S": "UE7PAG75L"},
"permission_level":{"S": "admin"}}'\
--endpoint-url http://localhost:8000
Replace USERS_TABLE
with whatever name you set in config.toml
.
Alternatively, you can directly edit the DynamoDB table via the AWS web
interface. Go to the DynamoDB service in the AWS web interface and open
the appropriate table. Click on the Items tab and then on “Create item”.
Make sure there’s a column for slack_id
and permission_level
,
where slack_id
is a String
with the appropriate value and
permission_level
is a String
with the value admin
.
8.2: Viewing a User¶
/rocket user view
The output of this command should be a stylish table displaying your Slack id and permissions level.
Now, you can continue with whatever testing you originally wanted to do. Remember to rebulid your Docker image every time you make a change!
Development Scripts¶
There are a few scripts in the scripts/
directory that aid in the
development of this project.
build_check.sh¶
scripts/build_check.sh
This is just the list of commands run to check the code for violations of Python style. It also runs the tests, and is the script that is run in our Github CI. Make sure to run before submitting a pull request!
This script also checks to see if the user is running DynamoDB locally, and if so, would include tests for it; if not, the tests that use DynamoDB will be deselected.
See git hooks.
port_busy.py¶
pipenv run python scripts/port_busy.py 8000
This is to check if a port is busy on the machine you are running on.
Used in place of nmap
for automatically checking if the port used
for local instances of DynamoDB is in use.
Exits with 0 if the port is in use.
Exits with 1 if there is an issue connecting with the port you provided.
Exits with 2 if the port you provided couldn’t be converted to an integer.
Exits with 3 if you didn’t provide exactly 1 argument.
Exits with 4 if the port is not already in use.
update.sh¶
scripts/update.sh
This should be run whenever any change to Pipfile
or
Pipfile.lock
occurs on your local copy of a branch. It updates any
changed dependencies into your virtual environment. This is equivalent
to the user running:
pipenv sync --dev
Which, coincidentally, require the same number of characters to be
typed. The script should ideally be run after any instance of
git pull
.
See git hooks.
download_dynamodb_and_run.sh¶
scripts/download_dynamodb_and_run.sh
This script downloads a copy of the latest local version of DynamoDB and
forks the process. It also sets up the environment in which you should
run it in using scripts/setup_localaws.sh
.
Please do not use this script; it is meant to be run by Github CI. Unless you enjoy having to download and run multiple DynamoDB processes.
setup_localaws.sh¶
scripts/setup_localaws.sh
This script automatically sets up your environment to better benefit a
local instance of DynamoDB. Only should be run once by users (though
running it multiple times would not hurt too too much). It requires
aws
to be installed through pipenv
.
docker_build.sh¶
scripts/docker_build.sh
This script builds a docker image rocket2-dev-img
, according to the
Dockerfile
. Equivalent to:
docker build -t rocket2-dev-img .
Make sure you have docker installed on your system beforehand.
docker_run_local.sh¶
scripts/docker_run_local.sh
This script runs a local docker image on your system, port 5000. Equivalent to:
docker run --rm -it -p 0.0.0.0:5000:5000 rocket2-dev-img
Make sure you have already built a rocket2-dev-img
, or have run
scripts/docker_build.sh
before-hand. docker
must also be
installed.
Makefile for Git Hooks¶
cd scripts
make
This script simply installs the pre-commit hooks and post-merge hooks.
build_check.sh
is copied to .git/hooks/pre-commit
, and
update.sh
is copied to .git/hooks/post-merge
.
After installation, every time you try to make a commit, all the tests
will be run automatically to ensure compliance. Every time you perform a
pull
or merge
or rebase
, pipenv
will try to sync all
packages and dependencies.
Makefile for Documentation¶
make clean html
This script builds all documentation and places the html into
_build/
directory. Should mostly be used to test your documentation
locally. Should be run within a pipenv shell
environment.
We use Python sphinx
to generate documentation from reStructuredText
and Markdown files in this project. To configure (and change versions
for the documentation), edit conf.py
. docs/index.rst
is the
index for all documentation.
Testing¶
Warning: This is no longer the most up-to-date documentation on how testing is done here. You may want to head over here for more up-to-date documentation on how we test things. You have been warned….
Running Pytest Efficiently¶
Test Driven Development… we hear professors preach about it during
lectures but we never got an opportunity to put it to good use until
Rocket2 came along. Unfortunately we got over excited and wrote A LOT of
tests. Running them all every time is a bit painful, that’s where
@pytest.mark
comes in. pytest.mark
allows you to label your
tests to run them in groups.
We only have tests that test the functions by themselves. Features that involve multiple parts (such as a new command involving Slack, Github, and the database) should be tested manually as well.
Run all the tests¶
pytest
Run only db tests¶
pytest -m db
Run all tests except database tests¶
pytest -m "not db"
Testing the Database¶
What are environment variables? Variables for the environment of course! These variables set up the environment for testing. Rocket2 uses them because we have both a local and a sever DynamoDB database and each require an extra variable to get everything working.
Run local DynamoDB¶
We use the AWS_LOCAL
environment variable to indicate if we want to
run DynamoDB locally or on a server. Change AWS_LOCAL = 'True'
to
use local DynamoDB.
If AWS_LOCAL == 'True'
but you did not start an instance of local
DynamoDB, scripts/build_check.sh
will automatically skip all
database tests.
This is the recommended way for unit testing.
Run server DynamoDB¶
To run the server DynamoDB we need to set the AWS_REGION
and obtain
AWS_ACCESS_KEYID
, AWS_SECRET_KEY
, and GITHUB_KEY
.
This is the recommended way for testing everything (not unit testing, but testing the slack commands themselves). Click here to learn how to set up a full development environment (including the testing part).
Development Tutorials¶
Create an User Model for DynamoDB database¶
A quick guide run through how Rocket2 takes in a command to generate a model that will be stored onto the database.
So you just joined Launchpad and want to add yourself to Rocket2. You go on slack and starts to talk to the Rocket2 bot, but what should you say? To get started, here’s a command you can enter:
command¶
A slack user calls Rocket2 to edit their information.
# SLACK_ID will be the current user's slack id.
# For this example, let's assume the slack id to be `StevenU`
/rocket user edit --name "Steven Universe" --email "su@gmail.com"
Yay! You have done what you were told to do, but wait! As a curious software developer, you’re curious about what makes Rocket2 tick. How exactly is your information saved onto Rocket2? Well, for every member added to Rocket2, a user model gets created.
model¶
An User model is constructed from the information the user input. Unfilled parameters will remain empty.
# To construct a User model with Slack ID 'StevenU'
steven_universe = User('StevenU')
steven_universe.email = 'su@gmail.com'
# To check if this user is valid.
User.is_valid(steven_universe) # returns true
# To get a user's permission level.
steven_universe.permissions_level # returns Permissions.member
Launchpad is growing every year, so there are a lot of user, hence a lot of user models. We have to be able to keep track and organize everyone, so that’s where database comes in. We create a table for every type of model, so in this case we’ll create a user table to store all users.
database (db)¶
Instead of using dynamodb.py
to handle our User model, we will use
facade.py
so we avoid becoming dependent on a single database. In
the future, this allows us to easily switch to using other databases.
# To store an user into the database.
facade.store(steven_universe)
# To retrieve an user from the database.
facade.retrieve(User, 'StevenU') # returns steven_universe user model
# If we try to retrieve a non-existent: user, a LookupError will be thrown.
facade.retrieve(User, 'fakeU') # returns 'User fakeU not found'
# To query an user based on a parameter, a list of matching Users will be
# returned.
facade.query(User, [('name', 'Steven Universe')]) # returns [steven_universe]
# To query an user based on a non-existent parameter, an empty list will be
# returned.
facade.query(User, [('email', 'fakeemail@gmail.com')]) # returns []
# To query an user without parameters, all the users will be returned
facade.query(User, []) # returns [steven_universe, second_user]
Create a Scheduler Module¶
So, you want to write a module and add it to the ever-growing list of modules that run periodically for rocket 2? Well, you have come to the right place.
A very good example module can be found in the
app/scheduler/modules/random_channel.py
source file. I recommend
that you read it before starting development (don’t worry, it’s very
short).
Structure¶
All scheduler modules are to be placed in the app/scheduler/modules/
directory. As Python source files, of course. These files should house
the module class. Every class must inherit ModuleBase
.
Since you inherit the ModuleBase
class, you must implement the
following methods:
get_job_args: A dictionary of job configuration arguments to be passed into the scheduler.
do_it: A function that actually does the thing you want to do every time the conditions you specified in the job configuration mentioned above.
Job arguments¶
As you can see from the example, the following job arguments are returned:
{'trigger': 'cron',
'day_of_week': 'sat',
'hour': 12,
'name': self.NAME}
Our trigger type is cron
, meaning that it is supposed to fire once
every time the rest of the arguments fit. day_of_week
means which
day it is supposed to fire. hour
means which hour on that day it is
supposed to fire. And every job has to have a name, which is specified
in the name
argument. For a more detailed look at the different
types of arguments and different trigger types that aren’t discussed
here, have a look at the APScheduler
documentation.
Firing the module¶
The function do_it
is called whenever it is time to execute the job.
You can use it to periodically message people, periodically check
statistics, poll Github, you name it.
Adding your module to the scheduler¶
To actually have the scheduler execute and remember your module (and
job), you must add the job to the scheduler. This can be achieved by
adding your module into the scheduler via the function __add_job
within the function __init_periodic_tasks
. You can see that we
already have initialized our beloved RandomChannelPromoter
in that
function, so just follow along with your own module.
And look! That wasn’t all that bad now wasn’t it??
Architecture¶

Our Flask server serves to handle all incoming Slack events, slash command, and
Github webhooks. Slash commands are handled by app.controller.command.parser
,
and the remaining events and webhooks are handled by app.controller.webhook.github.core
and app.controller.webhook.slack.core
.
We store our data in an Amazon DynamoDB, which can be accessed directly by the
database facade db.dynamodb.DynamoDB
.
We treat GitHub itself as the sole source of truth. Any modifications done on the Github side (e.g. changing team names, adding/removing team members, creating/deleting teams, etc.) is reflected into the database. Whenever an attempt is made at modifying the existing teams (e.g. using a slash command to add/remove members, create/delete teams, edit teams), the changes are made using the Github API, and then done on our database.
We run a cron-style scheduler that execute specific tasks at regular intervals. To learn how to add tasks and modules to it, have a look at this tutorial.
Configuration Reference¶
We use environmental variables for all of our configuration-related
things. A sample .env
file (which is what pipenv
looks for when
it tries to launch) can be found at sample-env
. Here is how each
variable works. Note: all variables are strings.
For variables that require newlines (such as signing keys), replace the
newlines with \n
. You can use the following command on most systems
to generate such a string:
awk '{printf "%s\\n", $0}' $FILE
For JSON variables, you can just remove the newlines:
awk '{printf "%s", $0}' $FILE
SLACK_SIGNING_SECRET¶
Signing secret of the slack app. Can be found in the basic information tab of your slack app (api.slack.com/apps).
SLACK_API_TOKEN¶
The Slack API token of your Slack bot. Can be found under OAuth & Permissions tab of your slack app (under the name “Bot user OAuth access token”).
The following permission scopes are required:
channels:read
channels:manage
chats:write
users.profile:read
users:read
commands
groups:read
im:write
You must also configure a slash command integration as well (under
“Slash commands”) for the URL path /slack/commands
of your Rocket
instance.
SLACK_NOFICIATION_CHANNEL¶
Name of the channel you want to have our rocket 2 slack bot to make service notifications in.
SLACK_ANNOUNCEMENT_CHANNEL¶
Name of the channel you want to have our rocket 2 slack bot to make announcements in.
GITHUB_APP_ID¶
The ID of your Github app (found under your Github organization settings -> Developer Settings -> Github Apps -> Edit).
GITHUB_ORG_NAME¶
The name of your Github organization (the string in the URL whenever you go to the organization.
GITHUB_DEFAULT_TEAM_NAME¶
The name of the GitHub team in your organization that all users should
be added to. Optional, defaults to all
.
GITHUB_ADMIN_TEAM_NAME¶
The name of the GitHub team in your organization that should be automatically promoted to Rocket administrators. Optional.
Note that this does not mean all Rocket administrators will be added to this team.
GITHUB_LEADS_TEAM_NAME¶
The name of the GitHub team in your organization that should be automatically promoted to Rocket team leads. Optional.
Note that this does not mean all Rocket team leads will be added to this team.
GITHUB_WEBHOOK_ENDPT¶
The path GitHub posts webhooks to. Note that the following events must be enabled (configured in GitHub app settings > “Permissions & events” > “Subscribe to events”):
Membership
Organization
Team
Team add
When configuring webhooks, provide the URL path /slack/commands
of
your Rocket instance.
GITHUB_WEBHOOK_SECRET¶
A random string of characters you provide to Github to help further obfuscate and verify that the webhook is indeed coming from Github.
GITHUB_KEY¶
The Github app signing key (can be found under Github organization settings -> Developer Settings -> Github Apps -> Edit (at the bottom you generate and download the key)). Paste the contents of the file as a string. See deployment for troubleshooting.
The following permissions must be set to “Read & Write” for the associated GitHub app (configured in GitHub app settings > “Permissions & events” > “Organization permissions”):
Organization members
AWS_ACCESS_KEYID¶
The AWS access key id.
AWS_SECRET_KEY¶
The AWS secret key.
AWS_*_TABLE¶
The names of the various tables (leave these as they are).
AWS_REGION¶
The region where the AWS instance is located (leave these as they are).
AWS_LOCAL¶
Point all AWS DynamoDB requests to http://localhost:8000
. Optional,
and defaults to False
.
GCP_SERVICE_ACCOUNT_CREDENTIALS¶
Service Account credentials for Google Cloud API access. Optional, and defaults to disabling related features.
Required scopes when credentials are provided:
https://www.googleapis.com/auth/drive
- used for synchronizing Drive folder permissions
For GSuite users, refer to this guide to set up service account access to your domain.
GCP_SERVICE_ACCOUNT_SUBJECT¶
User to emulate for GCP requests. Optional, and defaults to using your service account’s identity. This feature requires domain-wide authority to be delegated to your service account - refer to this guide.
Database Reference¶
users
Table¶
The users
table stores all the users. With DynamoDB, we only need to
specify a fixed attribute to be the primary index. In this case, the
user’s slack_id
is the primary index. All other attributes are
specified in the model/user.py
file, and are also listed here:
Attribute Name |
Description |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The user’s permission level is one of [member
, admin
,
team_lead
].
teams
Table¶
The teams
table stores all teams where github_team_id
is the
primary index. All other attributes are specified in the
model/team.py
file, and are also listed here:
Attribute Name |
Description |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
projects
Table¶
The projects
table stores all projects where project_id
is the
primary index. All other attributes are specified in the
model/project.py
file, and are also listed here:
Attribute Name |
Description |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Deployment¶
Deployment Process¶
The following should be read as more of a reference than a guide. To deploy Rocket 2, you must follow the steps as if you were building it for local use, except some tweaks in regards to where it goes and more tooling-specific details mentioned below.
Hosting¶
Rocket 2 is currently hosted by an AWS EC2 t2.micro instance. Since this is a single-threaded application with a single worker thread, there is not much of a reason to go for anything more. Note: Adding more worker threads may cause “minor” issues such as the scheduler running more than once, weird exceptions, and may prevent the server from running in some cases, which is why increasing the number of worker threads beyond 1 is not recommended.
If need-be, Inertia can help provision an instance for you.
Should you wish to set up your own Rocket 2 instance for deployment, you
should first be able to set up a Rocket 2 instance for testing on a
local computer with ngrok
forwarding. If you have successfully set
up an instance on a remote computer, you may still want to have a look.
For those of you who don’t want too much of a hassle, hosting via Heroku is also a valid option, as Heroku does continuous deployment without the need of setting up Inertia, and also has built-in SSL so you don’t need to set anything up. Be wary, however, that Heroku is almost twice as expensive as an AWS EC2 t2.micro instance.
Do note that you must set the environmental variables in the provided
settings page if you are to host via Heroku. For details regarding how
you would input the GITHUB_KEY
, please see below.
SSL¶
Before deploying for the first time, you must set up SSL and
configuration for Nginx, which we are using as a proxy server. This can
be done by running the scripts/setup_deploy.sh
script. This runs the
official Let’s Encrypt container to
request SSL certificates, sets up a cronjob to periodically re-validate
them, and copies nginx.conf
to the correct location. Do note that
the Let’s Encrypt container needs to use port 443, so if you have
another process or container using that port, you will need to kill it
before running the set up script.
Inertia¶
For UBC Launch Pad, we continuously deploy off the ec2-release
branch on Github using UBC Launch Pad’s
Inertia. This will pull
the repo when changes are merged, rebuild the containers from
docker-compose.yml
, and redeploy.
When deploying with Inertia, make sure that you are using a stable version of Inertia.
Since we have changed from using .toml
configuration files to using
environmental variables for configuration, you must inject them using
inertia {some name} env set AWS_LOCAL False
and the like. If you
already have all your environmental variables set up in your .env
file, you can send the entire file over with
inertia {some name} send .env
.
GITHUB_KEY¶
The GITHUB_KEY
is merely the GPG private key used to sign Github API
requests. We simply shove the entire file into a string and use it in
the environmental variable. Do note that doing this on the command line
is somewhat difficult because inertia
would treat the dashes --
in the string as flags and get confused. Another thing to watch out for
is that the command line ignores the new lines in the string. The
current working method of doing this is to pass in the entire string
with a single quote (which means that every symbol is taken literally),
then for every dash in the string, we add a forward slash \
in
front. We then replace all new lines with the literal \n
.
Our configuration code replaces these instances of \-
and \n
with actual dashes and new lines.
Note that these replacements are not necessary on Heroku and you can simply copy and paste the contents of the key file directly into the box provided.
If you are using the .env
file approach, you only need to replace
the new lines and not the dashes.
Docker Compose¶
Our main deployment configuration is contained in
docker-compose.yml
. We deploy an Nginx container to serve as a
proxy, as well as building and running a Rocket 2 container. The Nginx
proxy exposes ports 80 and 443, for HTTP/S, which must also be
accessible from the outside world. The Rocket 2 container exposes port
5000, as Gunicorn is listening on this port; this should not be
accessible to the outside world.
Note that Docker Compose has a rather complex networking utility. In
particular, note that to access HTTP endpoints in other composed
containers, you must reference them by their service name in
docker-compose.yml
, not via localhost. This is already handled in
nginx.conf
.
Pure Docker¶
One deployment option is to use the standalone Docker image:
docker pull ghcr.io/ubclaunchpad/rocket2:latest
docker run --rm -it -p 0.0.0.0:5000:5000 --env-file .env ghcr.io/ubclaunchpad/rocket2
Other Build Tools¶
Github Actions CI¶
Github Actions CI is a
continuous integration service that is used to build and test software
projects hosted on Github. To configure Github CI, a file
pythonpackage.yml
needs to be added to .github/workflows/
. This
YAML file will contain the commands for the automated tests that needs
to run.
Every time a branch gets pushed into github, Github CI starts a job. A job is where Github clones the GitHub repository into a new virtual environment to test the code.
Docker¶
Docker is a program that run software packages called containers. Every container is isolated from each other and is a bundle (also known as image) of their own tools, applications, libraries and configuration files. However, containers are able to also communicate with each other through channels, and all containers are run by a single OS kernel. We use Docker in Rocket2 to make deployment to the server easier.
Docker is composed of 3 parts: Container, Services, and Stack.
Dockerfile
defines the container. Inside Dockerfile
is the
environment that would be set up. Inside the container for Rocket2, we
have a copy of our app, and all the dependencies and the virtual
environment installed.
docker-compose.yml
defines the services that allow multiple
containers to run together.
Docker is different than virtual machines because it can run multiple containers using only one kernel which makes it more lightweight.
User Command Reference¶
Commands that manipulate user data. Remember that parameters with whitespace must be enclosed in quotation marks.
Options¶
/rocket user {add, edit, view, help, delete}
Add¶
/rocket user add [-f|--force]
Add the current user into the database. This command by default does not
overwrite users that have already been entered into the database. By
using the -f
flag, you force Rocket to overwrite the entry in
the database, if any.
Edit¶
/rocket user edit [--name NAME] [--email EMAIL] [--pos POSITION]
[--github GITHUB_HANDLE] [--major MAJOR]
[--bio BIOGRAPHY]
[--permission {member,team_lead,admin}]
Allows user to edit their Launch Pad profile. Admins and team leads can
edit another user’s Launch Pad profile by using [--username SLACK_ID]
option. SLACK_ID
is the @
-name, for easy slack autocomplete.
If a user edits their Github handle, Rocket will also add the handle to Launch Pad’s Github organization.
# Normal use
/rocket user edit --name "Steven Universe" --email "su@gmail.com"
# Admin/Team lead use
/rocket user edit --username @s_universe --name "Steven Universe"
Admins can easily promote other admins or team leads.
/rocket user edit --username @s_universe --permission admin
/rocket user edit --username @s_universe --permission team_lead
# Demotion
/rocket user edit --username @s_universe --permission member
View¶
/rocket user view [--username SLACKID] [--github GITHUB]
[--email EMAIL] [--inspect]
Display information about a user. SLACKID
is the @
-name, for
easy slack autocomplete. If SLACKID
is not specified, this command
displays information about the one who ran the command instead. You can also
specify a user’s Github username or a user’s email.
If the –inspect flag is used, this command lists the teams that the user is a part of, along with the teams that this user is leading, if any.
# Lookup via Github username, listing teams user is a part of
/rocket user view --github octoverse --inspect
Delete (Admin only)¶
/rocket user delete SLACK_ID
Permanently delete a member’s Launch Pad profile. Can only be used by
admins. SLACK_ID
is the @
-name, for easy slack autocomplete.
Team Command Reference¶
Commands that manipulate team data. Remember that parameters with whitespace must be enclosed by quotation marks.
Options¶
/rocket team {list, view, help, create, edit, add, remove, lead, delete}
Create (Team Lead and Admin only)¶
/rocket team create GITHUB_TEAM_NAME [--name DISPLAY_NAME]
[--platform PLATFORM]
[--channel CHANNEL]
[--lead SLACK_ID]
Create a new team with a Github team name and optional display name. The
user who runs the command will be automatically added to team as Team
Lead. If the --lead
flag is used, user with SLACK_ID
will be
added as Team Lead instead. If the --channel
flag is used, all
members in specified channel will be added. ‘SLACK_ID’ is the
@
-name, for easy slack autocomplete.
We use Github API to create the team on Github.
The Github team name cannot contain spaces.
/rocket team create "struddle-bouts" --name "Struddle Bouts" --channel @brussel_sprouts
Edit (Team Lead* and Admin only)¶
/rocket team edit GITHUB_TEAM_NAME [--name DISPLAY_NAME] [--platform PLATFORM]
Edit the properties of a specific team. Team Leads can only edit the teams that they are a part of, but admins can edit any teams.
Add (Team Lead* and Admin only)¶
/rocket team add GITHUB_TEAM_NAME SLACK_ID
Add a user to the team. Team Leads can only add users into teams that
they are a part of, but admins can add users to any team. SLACK_ID
is the @
-name, for easy slack autocomplete.
Users will be added to the teams on Github as well.
/rocket team add struddle-bouts @s_universe
Remove (Team Lead* and Admin only)¶
/rocket team remove GITHUB_TEAM_NAME SLACK_ID
Remove a user from a team, removes them as Team Lead if they were one.
Team Leads can only remove users from teams that they are a part of, but
admins can remove users from any team. SLACK_ID
is the @
-name,
for easy slack autocomplete.
Users will be removed from the teams on Github as well.
Lead (Team Lead* and Admin only)¶
/rocket team lead GITHUB_TEAM_NAME SLACK_ID [--remove]
Adds a user as Team Lead, and adds them to team if not already added. If
--remove
flag is used, will remove user as Team Lead, but not from
the team. Team Leads can only promote/demote users in teams that they
are part of, but admins can promote/demote users in any team. ‘SLACK_ID’
is the @
-name, for easy slack autocomplete.
Delete (Team Lead* and Admin only)¶
/rocket team delete GITHUB_TEAM_NAME
Permanently delete a team. Team Leads can only delete teams that they are a part of, but admins can delete any team.
Project Command Reference¶
Commands to do with projects. Remember that parameters with whitespace must be enclosed by quotation marks.
Options¶
/rocket project {list, view, help, create, unassign, edit, assign, delete}
Create (Team Lead and Admin only)¶
/rocket project create GH_REPO GITHUB_TEAM_NAME [--name DISPLAY_NAME]
Creates a new project from the given repo. Fails if the caller is not the team lead of the specified team or an admin.
Unassign (Team Lead and Admin only)¶
/rocket project unassign PROJECT_ID
Unassigns the given project. Fails if the caller is not the team lead of the team assigned to the project or if the caller is not an admin.
Assign (Team Lead and Admin only)¶
/rocket project assign PROJECT_ID GITHUB_TEAM_NAME [-f]
Assigns the project to the team. Fails if another team is assigned the
project. If -f
flag is given, can reassign even if another team is
already assigned the project. Fails if the caller is not the team lead
of the team to assign the project to or if the caller is not an admin.
Delete (Team Lead and Admin only)¶
/rocket project delete PROJECT_ID [-f]
Delete the project from database. An error occurs if the project is
currently assigned. If -f
flag is given, can be deleted even if a
team is assigned. Fails if the caller is not the team lead project’s
assigned team or if the caller is not an admin.
Karma Command Reference¶
Command to giveth or taketh away a user’s karma
Options¶
For normal users¶
Add 1 karma to user¶
/rocket @user ++
View a user’s karma¶
/rocket karma view @user
For admin only¶
Set user karma¶
/rocket karma set @user {amount}
Reset all user karma¶
/rocket karma reset --all
Examples¶
# normal user
/rocket @coolkid1 ++ #adds 1 karma to coolkid1
/rocket karma view @coolkid1 #view how much karma coolkid1 has
# admin only
/rocket karma set @coolkid1 5 #sets coolkid's karma to 5
/rocket karma reset --all #resets all users karma to 1
Configuration¶
Contain the dictionaries of configurations for all needed services.
-
class
config.
Config
¶ Load important informations from environmental variables.
We load the information (secret keys, access keys, paths to public/private keys, etc.) from the environment. Pipenv already loads from the environment and from the .env files.
-
__init__
()¶ Load environmental variables into self.
- Raises
MissingConfigError exception if any of the env variables aren’t found
-
Database¶
Commonly used database utilities¶
Database utilities, for functions that you use all the time.
-
db.utils.
get_team_by_name
(dbf, gh_team_name)¶ Query team by github team name.
Can only return a single team. If there are no teams with that name, or there are multiple teams with that name, we raise an error.
- Raises
LookupError if the calling user, user to add, or specified team cannot be found in the database
- Raises
RuntimeError if more than one team has the specified team name
- Return type
- Returns
Team if found
- Parameters
dbf (
db.facade.DBFacade
) –gh_team_name (
str
) –
-
db.utils.
get_team_members
(dbf, team)¶ Query users that are members of the given team.
- Return type
typing.List
[app.model.user.User
]- Returns
Users that belong to the team
- Parameters
dbf (
db.facade.DBFacade
) –team (
app.model.team.Team
) –
-
db.utils.
get_users_by_ghid
(dbf, gh_ids)¶ Query users by github user id.
- Return type
typing.List
[app.model.user.User
]- Returns
List of users if found
- Parameters
dbf (
db.facade.DBFacade
) –gh_ids (
typing.List
[str
]) –
Database Facade¶
-
class
db.facade.
DBFacade
¶ A database facade that gives an overall API for any databases.
Currently, we plan on having DynamoDB, but other databases, such as MongoDB or Postgres are also being considered. Please use this class instead of
db/dynamodb.py
, because we might change the databases, but the facade would stay the same.-
abstract
bulk_retrieve
(Model, ks)¶ Retrieve a list of models from the database.
Keys not found in the database will be skipped. Should be at least as fast as multiple calls to
.retrieve
.- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrieveks (
typing.List
[str
]) – retrieve based on this key (or ID)
- Return type
typing.List
[~T]- Returns
a list of models
Model
-
abstract
delete
(Model, k)¶ Remove an object from a table.
- Parameters
Model (
typing.Type
[~T]) – table type to remove the object fromk (
str
) – ID or key of the object to remove (must be primary key)
-
abstract
query
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have all of the attributes specified in the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query(Team, [('members', 'abc123'), ('members', '231abc')])
- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
abstract
query_or
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have one of the attributes specified in the parameters. Some might say that this is a union of the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query_or(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query_or(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query_or(Team, [('members', 'abc123'), ('members', '231abc')])
The above would get you the teams that contain either member
abc123
or231abc
.- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
abstract
retrieve
(Model, k)¶ Retrieve a model from the database.
- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrievek (
str
) – retrieve based on this key (or ID)
- Raises
LookupError if key is not found
- Return type
~T
- Returns
a model
Model
if key is found
-
abstract
store
(obj)¶ Store object into the correct table.
Object can be of type
app.model.user.User
,app.model.team.Team
, orapp.model.project.Project
.- Parameters
obj (~T) – Object to store in database
- Return type
bool
- Returns
True if object was stored, and false otherwise
-
abstract
DynamoDB¶
-
class
db.dynamodb.
DynamoDB
(config)¶ Handles calls to database through API.
Please do not use this class, and instead use
db.facade.DBFacade
. This class only works on DynamoDB, and should not be used outside of the facade class.- Parameters
config (
config.Config
) –
-
class
Const
(config)¶ A bunch of static constants and functions.
Used to convert between Python objects and the DDB table names, object attributes and table column names.
- Parameters
config (
config.Config
) –
-
__init__
(config)¶ Initialize the constants.
- Parameters
config (
config.Config
) –
-
get_key
(table_name)¶ Get primary key of the table name.
- Parameters
cls – the name of the table
table_name (
str
) –
- Raises
TypeError if table does not exist
- Return type
str
- Returns
primary key of the table
-
get_set_attrs
(table_name)¶ Get class attributes that are sets.
- Parameters
cls – the table name
table_name (
str
) –
- Raises
TypeError if table does not exist
- Return type
typing.List
[str
]- Returns
set attributes
-
get_table_name
(cls)¶ Convert class into corresponding table name.
- Parameters
cls (
typing.Type
[~T]) – EitherUser
,Team
, orProject
- Raises
TypeError if it is not either User, Team, or Project
- Return type
str
- Returns
table name string
-
__init__
(config)¶ Initialize facade using DynamoDB settings.
To avoid local tests failure when the DynamoDb server is used, an environment variable
config.aws_local
is read.if config.aws_local: # Connect to locally-run instance of DynamoDB pass else: # Connect to remote instance of DynamoDB pass
- Parameters
config (
config.Config
) – configuration used to initialize
-
bulk_retrieve
(Model, ks)¶ Retrieve a list of models from the database.
Keys not found in the database will be skipped. Should be at least as fast as multiple calls to
.retrieve
.- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrieveks (
typing.List
[str
]) – retrieve based on this key (or ID)
- Return type
typing.List
[~T]- Returns
a list of models
Model
-
check_valid_table
(table_name)¶ Check if table with
table_name
exists.- Parameters
table_name (
str
) – table identifier- Return type
bool
- Returns
boolean value, true if table exists, false otherwise
-
delete
(Model, k)¶ Remove an object from a table.
- Parameters
Model (
typing.Type
[~T]) – table type to remove the object fromk (
str
) – ID or key of the object to remove (must be primary key)
-
query
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have all of the attributes specified in the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query(Team, [('members', 'abc123'), ('members', '231abc')])
- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
query_or
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have one of the attributes specified in the parameters. Some might say that this is a union of the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query_or(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query_or(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query_or(Team, [('members', 'abc123'), ('members', '231abc')])
The above would get you the teams that contain either member
abc123
or231abc
.- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
retrieve
(Model, k)¶ Retrieve a model from the database.
- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrievek (
str
) – retrieve based on this key (or ID)
- Raises
LookupError if key is not found
- Return type
~T
- Returns
a model
Model
if key is found
-
store
(obj)¶ Store object into the correct table.
Object can be of type
app.model.user.User
,app.model.team.Team
, orapp.model.project.Project
.- Parameters
obj (~T) – Object to store in database
- Return type
bool
- Returns
True if object was stored, and false otherwise
MemoryDB¶
-
class
tests.memorydb.
MemoryDB
(users=[], teams=[], projs=[])¶ An in-memory database.
To be used only in testing. Do not attempt to use it in production. Used when a test requires a database, but when we aren’t specifically testing database functionalities.
Stored objects can be mutated by external references if you don’t drop the reference after storing.
- Parameters
users (
typing.List
[app.model.user.User
]) –teams (
typing.List
[app.model.team.Team
]) –projs (
typing.List
[app.model.project.Project
]) –
-
__init__
(users=[], teams=[], projs=[])¶ Initialize with lists of objects.
- Parameters
users (
typing.List
[app.model.user.User
]) – list of users to initialize the dbteams (
typing.List
[app.model.team.Team
]) – list of teams to initialize the dbprojs (
typing.List
[app.model.project.Project
]) – list of projects to initialize the db
-
bulk_retrieve
(Model, ks)¶ Retrieve a list of models from the database.
Keys not found in the database will be skipped. Should be at least as fast as multiple calls to
.retrieve
.- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrieveks (
typing.List
[str
]) – retrieve based on this key (or ID)
- Return type
typing.List
[~T]- Returns
a list of models
Model
-
delete
(Model, k)¶ Remove an object from a table.
- Parameters
Model (
typing.Type
[~T]) – table type to remove the object fromk (
str
) – ID or key of the object to remove (must be primary key)
-
query
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have all of the attributes specified in the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query(Team, [('members', 'abc123'), ('members', '231abc')])
- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
query_or
(Model, params=[])¶ Query a table using a list of parameters.
Returns a list of
Model
that have one of the attributes specified in the parameters. Some might say that this is a union of the parameters. Every item in parameters is a tuple, where the first element is the user attribute, and the second is the value.Example:
ddb = DynamoDb(config) users = ddb.query_or(User, [('platform', 'slack')])
If you try to query a table without any parameters, the function will return all objects of that table.:
projects = ddb.query_or(Project)
Attributes that are sets (e.g.
team.member
,project.github_urls
) would be treated differently. This function would check to see if the entry contains a certain element. You can specify multiple elements, but they must be in different parameters (one element per tuple).:teams = ddb.query_or(Team, [('members', 'abc123'), ('members', '231abc')])
The above would get you the teams that contain either member
abc123
or231abc
.- Parameters
Model (
typing.Type
[~T]) – type of list elements you’d wantparams (
typing.List
[typing.Tuple
[str
,str
]]) – list of tuples to match
- Return type
typing.List
[~T]- Returns
a list of
Model
that fit the query parameters
-
retrieve
(Model, k)¶ Retrieve a model from the database.
- Parameters
Model (
typing.Type
[~T]) – the actual class you want to retrievek (
str
) – retrieve based on this key (or ID)
- Raises
LookupError if key is not found
- Return type
~T
- Returns
a model
Model
if key is found
-
store
(obj)¶ Store object into the correct table.
Object can be of type
app.model.user.User
,app.model.team.Team
, orapp.model.project.Project
.- Parameters
obj (~T) – Object to store in database
- Return type
bool
- Returns
True if object was stored, and false otherwise
Factories¶
Interface¶
Amazon CloudWatch¶
-
class
interface.cloudwatch_metrics.
CWMetrics
(config)¶ - Parameters
config (
config.Config
) –
-
__init__
(config)¶ Initialize self. See help(type(self)) for accurate signature.
- Parameters
config (
config.Config
) –
Github¶
Utility classes for interacting with Github API via PyGithub.
-
class
interface.github.
DefaultGithubFactory
(app_id, private_key)¶ Default factory for creating interface to Github API.
- Parameters
app_id (
str
) –private_key (
str
) –
-
__init__
(app_id, private_key)¶ Init factory.
- Parameters
app_id (
str
) – Github Apps IDprivate_key (
str
) – Private key provided by Github Apps registration
-
create
()¶ Create instance of pygithub interface with Github Apps API token.
- Return type
github.MainClass.Github
-
class
interface.github.
GithubInterface
(github_factory, org)¶ Utility class for interacting with Github API.
- Parameters
github_factory (
interface.github.DefaultGithubFactory
) –org (
str
) –
-
__init__
(github_factory, org)¶ Initialize bot by creating Github object and get organization.
- Parameters
github_factory (
interface.github.DefaultGithubFactory
) –org (
str
) –
-
add_team_member
(username, team_id)¶ Add user with given username to team with id team_id.
- Parameters
username (
str
) –team_id (
str
) –
-
get_team_member
(username, team_id)¶ Return a team member with a username of username.
- Parameters
username (
str
) –team_id (
str
) –
- Return type
github.NamedUser.NamedUser
-
has_team_member
(username, team_id)¶ Check if team with team_id contains user with username.
- Parameters
username (
str
) –team_id (
str
) –
- Return type
bool
-
list_team_members
(team_id)¶ Return a list of users in the team of id team_id.
- Parameters
team_id (
str
) –- Return type
typing.List
[github.NamedUser.NamedUser
]
-
org_add_admin
(username)¶ Add member with given username as admin to organization.
- Parameters
username (
str
) –
-
org_add_member
(username)¶ Add/update to member with given username to organization.
If the user is already in the organization, don’t do anything.
- Parameters
username (
str
) –- Return type
str
-
org_create_team
(name)¶ Create team with given name and add to organization.
- Parameters
name (
str
) – name of team- Return type
int
- Returns
Github team ID
-
org_delete_team
(id)¶ Get team with given ID and delete it from organization.
- Parameters
id (
int
) –
-
org_edit_team
(key, name, description=None)¶ Get team with given ID and edit name and description.
- Parameters
key (
int
) – team’s Github IDname (
str
) – new team namedescription (
typing.Optional
[str
]) – new team description
-
org_get_team
(id)¶ Given Github team ID, return team from organization.
- Parameters
id (
int
) –- Return type
github.Team.Team
-
org_get_teams
()¶ Return array of teams associated with organization.
- Return type
typing.List
[app.model.team.Team
]
-
org_has_member
(username)¶ Return true if user with username is member of organization.
- Parameters
username (
str
) –- Return type
bool
-
org_remove_member
(username)¶ Remove member with given username from organization.
- Parameters
username (
str
) –
-
remove_team_member
(username, team_id)¶ Remove user with given username from team with id team_id.
- Parameters
username (
str
) –team_id (
str
) –
-
interface.github.
handle_github_error
(func)¶ Github error handler that updates Github App API token if necessary.
Interface to Github App API.
-
class
interface.github_app.
DefaultGithubAppAuthFactory
(app_id, private_key)¶ Factory for creating GithubAppAuth objects.
-
__init__
(app_id, private_key)¶ Initialize a Github App API auth factory.
- Parameters
app_id – Github Apps ID
private_key – Private key from application
-
create
()¶ Create an instance of GithubAppAuth.
-
-
class
interface.github_app.
GithubAppInterface
(app_auth_factory)¶ Interface class for interacting with Github App API.
-
class
GithubAppAuth
(app_id, private_key)¶ Class to encapsulate JWT encoding for Github App API.
-
__init__
(app_id, private_key)¶ Initialize Github App authentication.
-
is_expired
()¶ Check if Github App token is expired.
-
-
__init__
(app_auth_factory)¶ Initialize GithubAppInterface.
- Parameters
app_auth_factory – Factory for creating auth objects
-
create_api_token
()¶ Create installation token to make Github API requests.
See https://developer.github.com/v3/apps/#find-installations and https://developer.github.com/v3/apps/#create-a-new-installation-token for details.
- Returns
Authenticated API token
-
get_app_details
()¶ Retrieve app details from Github Apps API.
See https://developer.github.com/v3/apps/#get-the-authenticated-github-app for details.
- Returns
Decoded JSON object containing app details
-
class
Exceptions from interacting with Github API.
Google¶
Slack¶
Utility classes for interacting with Slack API.
-
class
interface.slack.
Bot
(sc, slack_channel='')¶ Utility class for calling Slack APIs.
- Parameters
sc (
slack.web.client.WebClient
) –slack_channel (
str
) –
-
__init__
(sc, slack_channel='')¶ Initialize Bot by creating a WebClient Object.
- Parameters
sc (
slack.web.client.WebClient
) –slack_channel (
str
) –
-
create_channel
(channel_name)¶ Create a channel with the given name.
:return name of newly created channel
-
get_channel_names
()¶ Retrieve list of channel names.
- Return type
typing.List
[str
]
-
get_channel_users
(channel_id)¶ Retrieve list of user IDs from channel with channel_id.
- Parameters
channel_id (
str
) –- Return type
typing.Dict
[str
,typing.Any
]
-
get_channels
()¶ Retrieve list of channel objects.
- Return type
typing.List
[typing.Any
]
-
send_dm
(message, slack_user_id)¶ Send direct message to user with id of slack_user_id.
- Parameters
message (
str
) –slack_user_id (
str
) –
-
send_event_notif
(message)¶ Send a message to the slack bot channel, usually for webhook notifs.
:param message to send to configured bot channel
-
send_to_channel
(message, channel_name, attachments=[])¶ Send message to channel with name channel_name.
- Parameters
message (
str
) –channel_name (
str
) –attachments (
typing.List
[typing.Any
]) –
Models¶
User¶
-
class
app.model.user.
User
(slack_id)¶ Represent a user with related fields and methods.
- Parameters
slack_id (
str
) –
-
__init__
(slack_id)¶ Initialize the user with a given Slack ID.
- Parameters
slack_id (
str
) –
-
classmethod
from_dict
(d)¶ Convert dict response object to user model.
- Parameters
d (
typing.Dict
[str
,typing.Any
]) – the dictionary representing a usercls (
typing.Type
[~T]) –
- Return type
~T
- Returns
the converted user model.
-
get_attachment
()¶ Return slack-formatted attachment (dictionary) for user.
- Return type
typing.Dict
[str
,typing.Any
]
-
classmethod
is_valid
(user)¶ Return true if this user has no missing required fields.
- Required fields for database to accept:
slack_id
permissions_level
- Parameters
user (~T) – user to check
cls (
typing.Type
[~T]) –
- Return type
bool
- Returns
true if this user has no missing required fields
-
classmethod
to_dict
(user)¶ Convert user object to dict object.
The difference with the in-built
self.__dict__
is that this is more compatible with storing into NoSQL databases like DynamoDB.- Parameters
user (~T) – the user object
cls (
typing.Type
[~T]) –
- Return type
typing.Dict
[str
,typing.Any
]- Returns
the dictionary representing the user
Team¶
-
class
app.model.team.
Team
(github_team_id, github_team_name, display_name)¶ Represent a team with related fields and methods.
- Parameters
github_team_id (
str
) –github_team_name (
str
) –display_name (
str
) –
-
__init__
(github_team_id, github_team_name, display_name)¶ Initialize the team.
Parameters are a valid Github team ID, team name and display name.
- Parameters
github_team_id (
str
) –github_team_name (
str
) –display_name (
str
) –
-
add_member
(github_user_id)¶ Add a new member’s Github ID to the team’s set of members’ IDs.
- Parameters
github_user_id (
str
) –
-
add_team_lead
(github_user_id)¶ Add a user’s Github ID to the team’s set of team lead IDs.
- Parameters
github_user_id (
str
) –
-
discard_member
(github_user_id)¶ Discard the member of the team with Github ID in the argument.
- Parameters
github_user_id (
str
) –
-
discard_team_lead
(github_user_id)¶ Remove a user’s Github ID to the team’s set of team lead IDs.
- Parameters
github_user_id (
str
) –
-
classmethod
from_dict
(d)¶ Convert dict response object to team model.
- Parameters
d (
typing.Dict
[str
,typing.Any
]) – the dictionary representing a teamcls (
typing.Type
[~T]) –
- Return type
~T
- Returns
the converted team model.
-
get_attachment
()¶ Return slack-formatted attachment (dictionary) for team.
-
get_basic_attachment
()¶ Return basic slack-formatted attachment (dictionary) for team.
-
has_member
(github_user_id)¶ Identify if any member has the ID specified in the argument.
- Parameters
github_user_id (
str
) –- Return type
bool
-
has_team_lead
(github_user_id)¶ Identify if user with given ID is a team lead.
- Parameters
github_user_id (
str
) –- Return type
bool
-
is_team_lead
(github_user_id)¶ Identify if user with given ID is a team lead.
- Parameters
github_user_id (
str
) –- Return type
bool
-
classmethod
is_valid
(team)¶ Return true if this team has no missing required fields.
- Required fields for database to accept:
github_team_name
github_team_id
- Parameters
team (~T) – team to check
cls (
typing.Type
[~T]) –
- Return type
bool
- Returns
true if this team has no missing required fields
-
classmethod
to_dict
(team)¶ Convert team object to dict object.
The difference with the in-built
self.__dict__
is that this is more compatible with storing into NoSQL databases like DynamoDB.- Parameters
team (~T) – the team object
cls (
typing.Type
[~T]) –
- Return type
typing.Dict
[str
,typing.Any
]- Returns
the dictionary representing the team
Project¶
-
class
app.model.project.
Project
(github_team_id, github_urls)¶ Represent a team project with team ID and related fields and methods.
- Parameters
github_team_id (
str
) –github_urls (
typing.List
[str
]) –
-
__init__
(github_team_id, github_urls)¶ Initialize the team project.
Project ID is a UUID generated by
uuid.uuid4()
.- Parameters
github_team_id (
str
) – the Github team ID associated with the projectgithub_urls (
typing.List
[str
]) – a set/list of URLs pointing to repositories
-
classmethod
from_dict
(d)¶ Return a project from a dict object.
- Parameters
d (
typing.Dict
[str
,typing.Any
]) – the dictionary (usually from DynamoDB)cls (
typing.Type
[~T]) –
- Return type
~T
- Returns
a Project object
-
get_attachment
()¶ Return slack-formatted attachment (dictionary) for project.
- Return type
typing.Dict
[str
,typing.Any
]
-
classmethod
is_valid
(p)¶ Return true if this project has no missing fields.
- Required fields for database to accept:
__project_id
__github_urls
- Parameters
project – project to check
cls (
typing.Type
[~T]) –p (~T) –
- Return type
bool
- Returns
true if this project has no missing fields
-
classmethod
to_dict
(p)¶ Return a dict object representing a project.
The difference with the in-built
self.__dict__
is that this is more compatible with storing into NoSQL databases like DynamoDB.- Parameters
p (~T) – the Project object
cls (
typing.Type
[~T]) –
- Return type
typing.Dict
[str
,typing.Any
]- Returns
a dictionary representing a project
Utilities for Testing¶
To read about the in-memory database used for testing without the local/remote database,
see tests.memorydb.MemoryDB
.
Some important (and often-used) utility functions.
-
tests.util.
create_test_admin
(slack_id)¶ Create a test admin user with slack id, and with all other attributes set.
Property
Preset
Slack ID
slack_id
Bio
I like puppies and kittens!
Email
Name
Iemann Atmin
Github
kibbles
Image URL
Major
Computer Science
Permission
Admin
Position
Adrenaline Junkie
- Parameters
slack_id (
str
) – The slack id string- Return type
- Returns
a filled-in user model (no empty strings)
-
tests.util.
create_test_project
(github_team_id, github_urls)¶ Create a test project with project ID, URLs, and all other attributes set.
Property
Preset
ID
SHA1(github_urls[0], time.time())
Team ID
github_team_id
Github URLs
github_urls
Display Name
Rocket2
Short Descrip.
Slack bot, team management, and onboarding system for…
Long Descrip.
Slack bot, team management, and onboarding system for…
Tags
python, docker, pipenv, waterboarding
Website
Appstore URL
¯\_(ツ)_/¯
Playstore URL
¯\_(ツ)_/¯
- Parameters
github_team_id (
str
) – The Github team IDgithub_urls (
typing.List
[str
]) – The URLs to all connected projects
- Return type
- Returns
a filled-in project model (no empty strings)
-
tests.util.
create_test_team
(tid, team_name, display_name)¶ Create a test team with team name, and with all other attributes the same.
Property
Preset
Github
tid
Name slug
team_name
Display
display_name
Platform
slack
Members
[‘abc_123’]
- Parameters
tid (
str
) – The github ID associated with the teamteam_name (
str
) – The github team name slugdisplay_name (
str
) – The github team name
- Return type
- Returns
a filled-in team model (no empty strings)
Utilities¶
Utility class for formatting Slack Messages.
-
utils.slack_msg_fmt.
wrap_code_block
(str)¶ Format code block.
-
utils.slack_msg_fmt.
wrap_emph
(str)¶ Format emph.
-
utils.slack_msg_fmt.
wrap_quote
(str)¶ Format quote.
-
utils.slack_msg_fmt.
wrap_slack_code
(str)¶ Format code.
The following are a few functions to help in handling command.
-
utils.slack_parse.
check_permissions
(user, team)¶ Check if given user is admin or team lead.
If team is specified and user is not admin, check if user is team lead in team. If team is not specified, check if user is team lead.
- Parameters
user (
app.model.user.User
) – user who’s permission needs to be checkedteam (
typing.Optional
[app.model.team.Team
]) – team you want to check that has user as team lead
- Return type
bool
- Returns
true if user is admin or a team lead, false otherwise
-
utils.slack_parse.
escape_email
(email)¶ Convert a string with escaped emails to just the email.
Before:
<mailto:email@a.com|email@a.com>
After:
email@a.com
Does nothing if the email is not escaped.
- Parameters
email (
str
) – email to convert- Return type
str
- Returns
unescaped email
-
utils.slack_parse.
escaped_id_to_id
(s)¶ Convert a string with escaped IDs to just the IDs.
Before:
/rocket user edit --username <@U1143214|su> --name "Steven Universe"
After:
/rocket user edit --username U1143214 --name "Steven Universe"
- Parameters
s (
str
) – string to convert- Return type
str
- Returns
string where all instances of escaped ID is replaced with IDs
-
utils.slack_parse.
ios_dash
(s)¶ Convert a string with a dash (—) to just double-hyphens (–).
Before:
/rocket user edit —name "Steven Universe"
After:
/rocket user edit --name "Steven Universe"
- Parameters
s (
str
) – string to convert- Return type
str
- Returns
string where all dashes are replaced with double-hyphens
-
utils.slack_parse.
is_slack_id
(slack_id)¶ Check if id given is a valid slack id.
- Parameters
id – string of the object you want to check
slack_id (
str
) –
- Return type
bool
- Returns
true if object is a slack id, false otherwise
-
utils.slack_parse.
regularize_char
(c)¶ Convert any unicode quotation marks to ascii ones.
Leaves all other characters alone.
- Parameters
c (
str
) – character to convert- Return type
str
- Returns
ascii equivalent (only quotes are changed)
Webhooks¶
Github¶
Handle GitHub webhooks.
-
class
app.controller.webhook.github.core.
GitHubWebhookHandler
(db_facade, gh_face, config)¶ Encapsulate the handlers for all GitHub webhook events.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –config (
config.Config
) –
-
__init__
(db_facade, gh_face, config)¶ Give handlers access to the database.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –config (
config.Config
) –
-
handle
(request_body, xhub_signature, payload)¶ Verify and handle the webhook event.
- Parameters
request_body (
bytes
) – Byte string of the request bodyxhub_signature (
str
) – Hashed signature to validatepayload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]- Returns
appropriate ResponseTuple depending on the validity and type of webhook
-
verify_hash
(request_body, xhub_signature)¶ Verify if a webhook event comes from GitHub.
- Parameters
request_body (
bytes
) – Byte string of the request bodyxhub_signature (
str
) – Hashed signature to validate
- Returns
True if the signature is valid, False otherwise
Define the abstract base class for a GitHub event handler.
-
class
app.controller.webhook.github.events.base.
GitHubEventHandler
(db_facade, gh_face, conf)¶ Define the properties and methods needed for a GitHub event handler.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –conf (
config.Config
) –
-
__init__
(db_facade, gh_face, conf)¶ Give handler access to the database facade.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –conf (
config.Config
) –
-
abstract
handle
(payload)¶ Handle a GitHub event.
- Parameters
payload (
typing.Dict
[str
,typing.Any
]) –- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
abstract property
supported_action_list
¶ Provide a list of all actions this handler can handle.
- Return type
typing.List
[str
]
Handle GitHub membership events.
-
class
app.controller.webhook.github.events.membership.
MembershipEventHandler
(db_facade, gh_face, conf)¶ Encapsulate the handler methods for GitHub membership events.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –conf (
config.Config
) –
-
handle
(payload)¶ Handle the event where a user is added or removed from a team.
- Parameters
payload (
typing.Dict
[str
,typing.Any
]) –- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
mem_added
(github_id, selected_team, team_name, github_username)¶ Help membership function if payload action is added.
- Parameters
github_id (
str
) –selected_team (
app.model.team.Team
) –team_name (
str
) –github_username (
str
) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
mem_remove
(github_id, selected_team, team_name)¶ Help membership function if payload action is removal.
- Parameters
github_id (
str
) –selected_team (
app.model.team.Team
) –team_name (
str
) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
Handle GitHub organization events.
-
class
app.controller.webhook.github.events.organization.
OrganizationEventHandler
(db_facade, gh_face, conf)¶ Encapsulate the handler methods for GitHub organization events.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –conf (
config.Config
) –
-
handle
(payload)¶ Handle when a user is added, removed, or invited to an organization.
If the member is removed, they are removed as a user from rocket’s db if they have not been removed already.
If the member is added or invited, do nothing.
- Parameters
payload (
typing.Dict
[str
,typing.Any
]) –- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
handle_added
(github_id, github_username, organization)¶ Help organization function if payload action is added.
- Parameters
github_id (
str
) –github_username (
str
) –organization (
str
) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
handle_invited
(github_username, organization)¶ Help organization function if payload action is invited.
- Parameters
github_username (
str
) –organization (
str
) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
handle_remove
(member_list, github_id, github_username)¶ Help organization function if payload action is remove.
- Parameters
member_list (
typing.List
[app.model.user.User
]) –github_id (
str
) –github_username (
str
) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
Handle GitHub team events.
-
class
app.controller.webhook.github.events.team.
TeamEventHandler
(db_facade, gh_face, conf)¶ Encapsulate the handler methods for GitHub team events.
- Parameters
db_facade (
db.facade.DBFacade
) –gh_face (
interface.github.GithubInterface
) –conf (
config.Config
) –
-
handle
(payload)¶ Handle team events of the organization.
This event is fired when a team is created, deleted, edited, or added or removed from a repository.
If a team is created, add or overwrite a team in rocket’s db.
If a team is deleted, delete the team from rocket’s db if it exists.
If a team is edited, overwrite the team’s fields or create the team if necessary.
If the team is added or removed from a repository, do nothing for now.
- Parameters
payload (
typing.Dict
[str
,typing.Any
]) –- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
team_added_to_repository
(github_id, github_team_name, payload)¶ Help team function if payload action is added_to_repository.
- Parameters
github_id (
str
) –github_team_name (
str
) –payload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
team_created
(github_id, github_team_name, payload)¶ Help team function if payload action is created.
- Parameters
github_id (
str
) –github_team_name (
str
) –payload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
team_deleted
(github_id, github_team_name, payload)¶ Help team function if payload action is deleted.
- Parameters
github_id (
str
) –github_team_name (
str
) –payload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
team_edited
(github_id, github_team_name, payload)¶ Help team function if payload action is edited.
- Parameters
github_id (
str
) –github_team_name (
str
) –payload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
-
team_removed_from_repository
(github_id, github_team_name, payload)¶ Help team function if payload action is removed_from_repository.
- Parameters
github_id (
str
) –github_team_name (
str
) –payload (
typing.Dict
[str
,typing.Any
]) –
- Return type
typing.Tuple
[typing.Union
[typing.Dict
[str
,typing.List
[typing.Dict
[str
,typing.Any
]]],str
,typing.Dict
[str
,typing.Any
]],int
]
Slack¶
Handle Slack events.
-
class
app.controller.webhook.slack.core.
SlackEventsHandler
(db_facade, bot)¶ Encapsulate the handlers for all Slack events.
- Parameters
db_facade (
db.facade.DBFacade
) –bot (
interface.slack.Bot
) –
-
__init__
(db_facade, bot)¶ Initialize all the required interfaces.
- Parameters
db_facade (
db.facade.DBFacade
) –bot (
interface.slack.Bot
) –
-
handle_team_join
(event_data)¶ Handle the event of a new user joining the workspace.
- Parameters
event_data (
typing.Dict
[str
,typing.Any
]) – JSON event data
License¶
MIT License
Copyright (c) 2018 UBC Launch Pad
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Code of Conduct¶
All contributors are required to adhere to the UBC Launch Pad Code of Conduct.