Slackbot using google cloud serverless functions
At my org, we wanted a simple Slack bot that posts a roundup of new channels created recently in the workspace to a channel. While writing this is easy enough, I wanted to do it using Google Cloud Functions with Python, trying to follow best practices as much as possible.
Here's how the overall flow will look like:
We want this roundup post triggered on some schedule (maybe daily), so the Cloud Scheduler is required to send an event to a Google Pub/Sub topic that triggers our cloud function, which queries the slack API to get channels details, filter recently created, and post it back to a slack channel. Secret Manager is used to securely store slack's bot token and signing secret.
Note that the credentials shown in any screenshots below are not valid.
Create the slack app
The first step will be to create the slack app. Go to https://api.slack.com and click on "Create an app". Choose "From scratch" in the first dialog; enter an app name and choose a workspace for your app in the second dialog. In the next screen, copy the "Signing Secret" from the "App Credentials" section and save it for later use.
Next, go to the "OAuth and Permissions" tab from the left sidebar, and scroll down to "Scopes" -> "Bot Token Scopes". Here, add the scopes:
channels:read
: required to query public channels and find their creation timeschat:write
: required to write to a channel (where the bot is invited)
Next, scroll up on the same screen and click "Install to Workspace" to install to your workspace. Click "Allow" in the next screen to allow the installation. Next, copy the "Bot User OAuth Token" from the "OAuth Tokens for Your Workspace" section on the same page and save it for later use.
💡Keep track of the Bot User OAuth Token and Signing Secret you copied above.
Post to a Slack channel from a Google Cloud Function
Next, we will try to use the credentials copied above to enable a Google Cloud Function to send a message to a Slack channel.
Google Cloud Basic Setup
We will use gcloud cli for the following sections, so install and initialize the Google Cloud CLI if not done yet. If you already have gcloud cli, run gcloud components update
to update it to the latest version.
Create a new project for this if required, or choose an existing project, set it as default, and export the project id as a shell environment for using later. Also export the region you want to use.
You will have to enable billing for this project to be able to use some of the functionality we require.
You may also have to enable the Secret Manager, Cloud Functions, Cloud Build, Artifact Registry, and Logging APIs if this is the first time you're using Functions in this project. Note that some services like Secret Manager need billing to be setup before they can be enabled.
Create a service account
By default, Cloud Functions uses a default service account as its identity for function execution. These default service accounts have the Editor role, which allows them broad access to many Google Cloud services. Of course, this is not recommended for production, so we will create a new service account for this and grant it the minimum permissions that it requires.
Store secrets and give permissions to service account
First, we need to store the secrets in Secret Manager.
And give our service account the roles/secretmanager.secretAccessor
role on these secrets.
Create and deploy the function
Here's a simple HTTP function that sends a message to slack on any HTTP call:
Assuming main.py
and requirements.txt
are present in src-v1
folder, deploy using:
💡We're using --allow-unauthenticated
here just to test it out. It will be removed in later sections.
Test it out
Once the deployment is complete, we can view the function logs using:
If everything was successful above, once of the recent log statements should say: Function has started
.
Next, add the bot to the #general
channel using /invite @ChannelBot
in the general channel on your slack workspace.
Next, find the service endpoint using:
This will give a URL like https://channelbot-send-to-slack-ga6Ofi9to0-uc.a.run.app
.
To trigger the channel post, just do curl ${SERVICE_URL}
. This should result in a test message from ChannelBot to the #general
channel.
Trigger via Google Pub/Sub
Now, instead of an unauthenticated HTTP trigger, we would like to trigger this via Google Pub/Sub. We would also like to pass the channel name and the message to post in the event.
Google Pub/Sub basics
Pub/Sub enables you to create systems of event producers and consumers, called publishers and subscribers. Publishers communicate with subscribers asynchronously by broadcasting events. Some core concepts:
- Topic. A named resource to which messages are sent by publishers.
- Subscription. A named resource representing the stream of messages from a single, specific topic, to be delivered to the subscribing application.
- Message. The combination of data and (optional) attributes that a publisher sends to a topic and is eventually delivered to subscribers.
- Publisher. An application that creates and sends messages to a single or multiple topics.
In
this section, we will create a topic, create a subscription for our
cloud function to listen to messages to that topic, and produce messages
manually to that topic using gcloud
cli. The message will
contain the channel name and message to post, and the cloud function
will post that message to the specified slack channel.
Create pub/sub topic
First, we need to create a topic.
Grant permissions to the service account
Next, we need to give the roles/pubsub.editor
role to the service account we're using for the function execution so that it can create a subscription to this pub/sub topic.
Update the function code
Here's the main.py
we'll need to listen to pub/sub events, extract channel
and text
, and sent it to slack:
Before deploying, we also need to enable the Eventarc API in this project.
Deploy and Test
Now, there's a slightly modified version of the deploy command to deploy this:
The main changes are:
- Changed entry-point to the new function
pubsub_handler
- Replaced
--trigger-http
with--trigger-topic
- Removed
--allow-unauthenticated
Before sending a pub/sub message, we will also need to give the roles/run.invoker
role to our service account to be able to trigger our newly deployed function.
To test this out, we can send a pub/sub message using gcloud cli:
Post new channels roundup using cloud scheduler
Manually post recently created channels
Now that we have gained the capability to trigger a message from pub/sub to slack, we can add some logic to fetch the recently created channels from slack and post it as a message on this trigger.
Here's the modified main.py
to do this:
After deploying this with the same command above (just change --source ./src-v2
to --source ./src-v3
), we can send a pub/sub event to trigger it:
And it posts a message like this:
Create schedule
Next, we want to periodically schedule this message. For this, we will configure a cron job in Google Cloud Scheduler to send a Pub/Sub event with the required parameters periodically.
Before we create a schedule, we will have to enable the Cloud Scheduler API
To schedule the Pub/Sub trigger at 1600 hours UTC time every day:
After this, a Pub/Sub event should be fired to the channelbot-pubsub
topic every day, which should result in a slack message to #general
with a list of channels created in the last day.
Closing Thoughts
Full code samples for this can be found in this github repo. I've also included a Makefile
with targets split into sections associated with the different steps in this post.
I also plan to follow this up with a part 2 where we will use slack's slash commands to allow the end-user of this bot to setup the channel and frequency of posting of the recent channels list, and even configure multiple schedules. Please comment below if this is something you will be interested in.
Interactions