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 /rocket in Slack

🔗

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:

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

_images/rocket2arch.png

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

slack_id

String; The user’s slack id

email

String; The user’s email address

github

String; The user’s Github handler

github_user_id

String; The user’s Github user ID

major

String; The subject major the user is in

position

String; The user’s position in Launch Pad

bio

String; A short (auto)biography

image_url

String; The user’s avatar image URL

permission_level

String; The user’s permission level

karma

Integer; The user’s karma points

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

github_team_id

String; The team’s Github ID

github_team_name

String; The team’s Github name

display_name

String; The teams’s display

platform

String; The team’s working platform

team_leads

String Set; The team’s set of team leads’ Github IDs

members

String Set; The team’s set of members’ Github IDs

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

project_id

String; The project’s unique SHA1 ID, salted with a timestamp

github_team_id

String; The team’s Github ID associated with the project

github_urls

String Set; A set of URLs pointing to project repositories

display_name

String; A name for the project

short_description

String; A short description that outlines the project

long_description

String; A longer and more in-depth description

tags

String Set; A set of tags taken from the Github repositories

website_url

String; A URL to the project’s website

medium_url

String; A URL to the project’s medium page

appstore_url

String; A URL to the project’s Apple Appstore page

playstore_url

String; A URL to the project’s Google Playstore page

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

Help

/rocket user help

Display options for the user commands.

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}

List

/rocket team list

Display a list of Github team names and display names of all teams.

View

/rocket team view GITHUB_TEAM_NAME

Display information and members of a specific team.

Help

/rocket team help

Display options for team commands.

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}

List

/rocket project list

Display a list of all projects.

View

/rocket project view PROJECT_ID

Displays details of project.

Help

/rocket project help

Displays options for project command.

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.

Edit

/rocket project edit PROJECT_ID [--name DISPLAY_NAME]

Edit the given project.

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
Help
Display options for karma commands
/rocket karma help

Commands

Commands Parser

User

Team

Token

Karma

Mention

I-Quit

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

exception config.MissingConfigError(missing_config_fields)

Exception representing an error while loading credentials.

__init__(missing_config_fields)

Initialize a new MissingConfigError.

Parameters

missing_config_fields – the missing config variables

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

app.model.team.Team

Returns

Team if found

Parameters
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
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

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 retrieve

  • ks (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 from

  • k (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 want

  • params (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 or 231abc.

Parameters
  • Model (typing.Type[~T]) – type of list elements you’d want

  • params (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 retrieve

  • k (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, or app.model.project.Project.

Parameters

obj (~T) – Object to store in database

Return type

bool

Returns

True if object was stored, and false otherwise

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]) – Either User, Team, or Project

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 retrieve

  • ks (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 from

  • k (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 want

  • params (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 or 231abc.

Parameters
  • Model (typing.Type[~T]) – type of list elements you’d want

  • params (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 retrieve

  • k (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, or app.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
__init__(users=[], teams=[], projs=[])

Initialize with lists of objects.

Parameters
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 retrieve

  • ks (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 from

  • k (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 want

  • params (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 or 231abc.

Parameters
  • Model (typing.Type[~T]) – type of list elements you’d want

  • params (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 retrieve

  • k (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, or app.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 ID

  • private_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
__init__(github_factory, org)

Initialize bot by creating Github object and get organization.

Parameters
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 ID

  • name (str) – new team name

  • description (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

Exceptions from interacting with Github API.

exception interface.exceptions.github.GithubAPIException(data)

Exception representing an error while calling Github API.

__init__(data)

Initialize a new GithubAPIException.

Parameters

data

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]) –

exception interface.slack.SlackAPIError(error)

Exception representing an error while calling Slack API.

__init__(error)

Initialize a new SlackAPIError.

Parameters

error – Error string returned from Slack API.

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 user

  • cls (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 team

  • cls (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 project

  • github_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

Permissions

class app.model.permissions.Permissions(value)

Enum to represent possible permissions levels.

member = 1
team_lead = 2
admin = 3
__str__()

Return the string without ‘Permissions.’ prepended.

Return type

str

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

admin@ubc.ca

Name

Iemann Atmin

Github

kibbles

Image URL

https://via.placeholder.com/150

Major

Computer Science

Permission

Admin

Position

Adrenaline Junkie

Parameters

slack_id (str) – The slack id string

Return type

app.model.user.User

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

https://github.com/ubclaunchpad/rocket2

Appstore URL

¯\_(ツ)_/¯

Playstore URL

¯\_(ツ)_/¯

Parameters
  • github_team_id (str) – The Github team ID

  • github_urls (typing.List[str]) – The URLs to all connected projects

Return type

app.model.project.Project

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 team

  • team_name (str) – The github team name slug

  • display_name (str) – The github team name

Return type

app.model.team.Team

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
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
__init__(db_facade, gh_face, config)

Give handlers access to the database.

Parameters
handle(request_body, xhub_signature, payload)

Verify and handle the webhook event.

Parameters
  • request_body (bytes) – Byte string of the request body

  • xhub_signature (str) – Hashed signature to validate

  • 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]

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 body

  • xhub_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
__init__(db_facade, gh_face, conf)

Give handler access to the database facade.

Parameters
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
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
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
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
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
__init__(db_facade, bot)

Initialize all the required interfaces.

Parameters
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.

Indices and tables