1. Overview
In this codelab, you'll learn how to build a Slack bot using the Botkit toolkit and run it on Google Cloud. You'll be able to interact with the bot in a live Slack channel.
What you'll learn
- How to create a bot custom integration in Slack
- How to secure your Slack secrets with Secret Manager
- How to deploy a Slack bot on Cloud Run, a fully managed compute platform that automatically scales your stateless containers
What you'll need
How will you use this tutorial?
How would you rate your experience with Google Cloud?
2. Setup and requirements
Self-paced environment setup
- Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.
- The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can always update it.
- The Project ID is unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference your Project ID (typically identified as
PROJECT_ID
). If you don't like the generated ID, you might generate another random one. Alternatively, you can try your own, and see if it's available. It can't be changed after this step and remains for the duration of the project. - For your information, there is a third value, a Project Number, which some APIs use. Learn more about all three of these values in the documentation.
- Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab won't cost much, if anything at all. To shut down resources to avoid incurring billing beyond this tutorial, you can delete the resources you created or delete the project. New Google Cloud users are eligible for the $300 USD Free Trial program.
Start Cloud Shell
While Google Cloud can be operated remotely from your laptop, in this tutorial you will be using Cloud Shell, a command line environment running in the Cloud.
Activate Cloud Shell
- From the Cloud Console, click Activate Cloud Shell .
If this is your first time starting Cloud Shell, you're presented with an intermediate screen describing what it is. If you were presented with an intermediate screen, click Continue.
It should only take a few moments to provision and connect to Cloud Shell.
This virtual machine is loaded with all the development tools needed. It offers a persistent 5 GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with a browser.
Once connected to Cloud Shell, you should see that you are authenticated and that the project is set to your project ID.
- Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list
Command output
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project
Command output
[core] project = <PROJECT_ID>
If it is not, you can set it with this command:
gcloud config set project <PROJECT_ID>
Command output
Updated property [core/project].
3. Enable the APIs
From Cloud Shell, enable the Artifact Registry, Cloud Build, Cloud Run, and Secret Manager APIs:
gcloud services enable \ artifactregistry.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ secretmanager.googleapis.com
This outputs a success message similar to this one:
Operation "operations/..." finished successfully.
Now, you're ready to prepare and deploy your application...
4. Create a Slack workspace
You will need a Slack workspace where you are allowed to create custom integrations. You can create a workspace for free if you do not already have one that you wish to use for this tutorial.
5. Create a Slack bot user
A bot user can listen to messages on Slack, post messages, and upload files. In this codelab, you will create a bot to post a simple greeting message.
Create a new Slack app
- Go to the Slack apps management page.
- Click the Create new app button in the upper-right corner.
- Give the app a name, such as "Kittenbot".
- Choose the Slack team where you want it installed.
Create a bot user
- Go to App Home on the left-side panel under Features
- Assign a scope to your bot token by clicking Review Scopes to Add
- Scroll down to Bot Token Scopes and click Add an OAuth Scope. Select
chat:write
to "Send messages as Kittenbot"
- Scroll up and click the Install App to your Workspace button.
- This will install the app to your team, add the bot user you just created, and generate a bot token.
- When prompted, click "Allow" to authorize the bot to chat in your workspace.
Enable messages and commands
- Scroll down to Show Tabs and make sure both options are enabled:
Get the client signing secret
- Go to Basic Information under Settings.
- Scroll down to Signing Secret, click Show, and then copy the secret to your clipboard:
- Save the secret in an environment variable:
CLIENT_SIGNING_SECRET=PASTE_THE_SIGNING_SECRET
Get the bot token
- Go to OAuth & Permissions under Features.
- Click the Copy button to copy the Bot User OAuth Access Token text into your clipboard.
- Save the bot token in an environment variable:
BOT_TOKEN=PASTE_THE_BOT_TOKEN
Don't worry. You can come back to this configuration page from the apps management page if you need to get these tokens again.
6. Secure your secrets
We want to ensure that your bot token and your client signing secret are stored securely. Hard-coding them in source code makes it likely to accidentally expose these secrets by publishing them to version control or embedding them in a docker image.
Secret Manager provides a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. Secret Manager provides a central place and single source of truth to manage, access, and audit secrets across Google Cloud.
Create your secrets
Save your client signing secret and bot token with the following commands:
- Client signing secret
echo -n $CLIENT_SIGNING_SECRET | gcloud secrets create client-signing-secret \ --replication-policy automatic \ --data-file -
- Bot token
echo -n $BOT_TOKEN | gcloud secrets create bot-token \ --replication-policy automatic \ --data-file -
Access your Secrets
Let's confirm that your secrets were created properly and your permissions are working. Access your secrets with the following commands:
echo $(gcloud secrets versions access 1 --secret client-signing-secret) echo $(gcloud secrets versions access 1 --secret bot-token)
You can also view and manage your secrets in the Google Cloud console.
7. Get the sample code
In Cloud Shell on the command-line, run the following command to clone the GitHub repository:
git clone https://github.com/googlecodelabs/cloud-slack-bot.git
Change directory into cloud-slack-bot/start
.
cd cloud-slack-bot/start
Understanding the Code
Open up the kittenbot.js
file with your preferred command line editor (nano, vim, emacs...) or with the following command to directly open the current folder in Cloud Shell Editor:
cloudshell workspace .
The kittenbot code has two main functions. One is to retrieve the secrets, and the other is to run the bot.
First, we import the dependencies:
kittenbot.js
const { Botkit } = require('botkit');
const {
SlackAdapter,
SlackEventMiddleware,
} = require('botbuilder-adapter-slack');
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
The SlackAdapter and SlackEventMiddleware are packages that extend Botkit and allow the bot to easily translate messages to and from the Slack API. The Secret Manager client will allow you to access the secrets you saved in an earlier step.
Next we have our function for retrieving the secrets:
/**
* Returns the secret string from Google Cloud Secret Manager
* @param {string} name The name of the secret.
* @return {Promise<string>} The string value of the secret.
*/
async function accessSecretVersion(name) {
const client = new SecretManagerServiceClient();
const projectId = process.env.PROJECT_ID;
const [version] = await client.accessSecretVersion({
name: `projects/${projectId}/secrets/${name}/versions/1`,
});
// Extract the payload as a string.
const payload = version.payload.data.toString('utf8');
return payload;
}
This function returns the string values of the secrets needed to authenticate the bot.
The next function initializes the bot:
/**
* Function to initialize kittenbot.
*/
async function kittenbotInit() {
const adapter = new SlackAdapter({
clientSigningSecret: await accessSecretVersion('client-signing-secret'),
botToken: await accessSecretVersion('bot-token'),
});
adapter.use(new SlackEventMiddleware());
const controller = new Botkit({
webhook_uri: '/api/messages',
adapter: adapter,
});
controller.ready(() => {
controller.hears(
['hello', 'hi', 'hey'],
['message', 'direct_message'],
async (bot, message) => {
await bot.reply(message, 'Meow. :smile_cat:');
}
);
});
}
The first part of the function configures the SlackAdapter with the secrets, and then specifies an endpoint for receiving messages. Then, once the controller is on, the bot will reply to any message containing "hello", "hi", or "hey" with "Meow. 😺".
Check out these specific parts in the app manifest:
package.json
{
// ...
"scripts": {
"start": "node kittenbot.js",
// ...
},
"engines": {
"node": "16"
},
// ...
}
You can deploy a Node.js app directly from source with Cloud Run. The following will happen under the hood:
- Cloud Run calls Cloud Build to build a container image (see Deploying from source code).
- If a
Dockerfile
is present in the source code directory, Cloud Build uses it to build a container image. - Since it is not, Cloud Build calls Buildpacks to analyze the source and auto-generate a production-ready image.
- Buildpacks detects the
package.json
manifest and builds a Node.js image. - The
scripts.start
field determines how the app is started. - The
engines.node
field determines the Node.js version of the container base image. - At deployment time, known security fixes are automatically applied.
You're ready to deploy the app!
8. Deploy the app
The Slack Events API uses webhooks to send outgoing messages about events. When you configure the Slack App, you'll have to provide a publicly accessible URL for the Slack API to ping.
Cloud Run is a good solution for hosting webhook targets. It allows you to use any language or runtime that you like and it provides concurrency, meaning your application will be able to handle much higher volume.
Retrieve your project ID
Define the PROJECT_ID environment variable:
PROJECT_ID=$(gcloud config get-value core/project)
Define your Cloud Run region
Cloud Run is regional, which means the infrastructure that runs your Cloud Run service is located in a specific region and is managed by Google to be redundantly available across all the zones within that region. Define the region you'll use for your deployment, for example:
REGION="us-central1"
Update the permissions
To be able to access secrets from Secret Manager, the Cloud Run service account needs to be granted the role roles/secretmanager.secretAccessor
.
First, save the default service account into an environment variable:
SERVICE_ACCOUNT=$(gcloud iam service-accounts list \ --format "value(email)" \ --filter "displayName:Compute Engine default service account")
Confirm you have the email address saved:
echo $SERVICE_ACCOUNT
The service account has the following format: PROJECT_NUMBER-compute@developer.gserviceaccount.com
.
Once you have the email address, enable the role for the service account:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT \ --role roles/secretmanager.secretAccessor
Deploy the app
A Cloud Run service exposes a unique endpoint and automatically scales the underlying infrastructure to handle incoming requests.
Deploy the app to Cloud Run:
gcloud run deploy kittenbot \ --source . \ --platform managed \ --region $REGION \ --set-env-vars PROJECT_ID=$PROJECT_ID \ --allow-unauthenticated
- This creates a service called
kittenbot
. - The
--source
option uses the current folder to build the application with Cloud Build. Cloud Build automatically detects the presence of thepackage.json
file. - You can alternatively define a default region with this command:
gcloud config set run/region $REGION
- You can also make Cloud Run managed by default with this command:
gcloud config set run/platform managed
- The
--set-env-vars
option sets the service environment variables. - The
--allow-unauthenticated
option makes the service publicly available.
The first time, you'll get a prompt to create an Artifact Registry repository. Tap Enter to validate:
Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in region [REGION] will be created. Do you want to continue (Y/n)?
This launches the upload of your source code to the Artifact Registry repository and the build of your container image:
Building using Dockerfile and deploying container ... * Building and deploying new service... Building Container. OK Creating Container Repository... OK Uploading sources... * Building Container... Logs are available at ...
Then, wait a moment until the build and deployment are complete. On success, the command line displays the service URL:
... OK Building and deploying new service... Done. OK Creating Container Repository... OK Uploading sources... OK Building Container... Logs are available at ... OK Creating Revision... Creating Service. OK Routing traffic... OK Setting IAM Policy... Done. Service [SERVICE]... has been deployed and is serving 100 percent of traffic. Service URL: https://SERVICE-PROJECTHASH-REGIONID.a.run.app
You can get the service URL with this command:
SERVICE_URL=$( \ gcloud run services describe kittenbot \ --platform managed \ --region $REGION \ --format "value(status.url)" \ ) echo $SERVICE_URL
The URL has the following format:
https://kittenbot-PROJECTHASH-REGIONID.a.run.app
This URL will be the base used to enable the Slack Events API. Copy it into your clipboard to use in the next step.
Your service is now live and publicly available! Go to the Cloud Run console for more information.
You can see when the last revision was created, how much traffic it's receiving, and look at the logs. If we click into the logs, we can see that the Botkit controller is on and ready to receive messages.
Now let's start sending messages from our Slack channel!
9. Enable Slack events
As we saw earlier, our kittenbot code specifies a relative endpoint for our webhook target.
kittenbot.js
const controller = new Botkit({
webhook_uri: '/api/messages',
adapter: adapter,
});
This means, our full URL will be the base part from the Cloud Run service, plus /api/messages
.
Enable Events
In the apps management page, go to the Events Subscriptions section on the sidebar, and toggle Enable Events on. Input your service URL:
PASTE_THE_SERVICE_URL/api/messages
Depending on how fast you type the URL in, it might try to verify before you're finished. If it fails, click "Retry."
Subscribe
Subscribe to all the message bot events.
Click Save Changes at the bottom of the page. You will be prompted to Reinstall Your App. Go through the prompts and click Allow.
At this point, your bot is fully integrated! Messages in the workspace will trigger Slack to send messages to your Cloud Run service, which will in turn respond with a simple greeting.
10. Test your bot
Send a direct message to Kittenbot:
Add kittenbot to your channel by entering "@kittenbot" and then clicking "Invite Them.":
Now everyone in your channel can interact with Kittenbot!
Each message in Slack triggers an event and sends an HTTP POST message to our Cloud Run service. If you take a look at the Cloud Run service logs, you'll see that each message corresponds to a POST entry in the log.
The kittenbot responds to each message with "Meow. 😺".
11. Bonus - Update your bot
This optional section should take a few minutes. Feel free to skip directly to Cleanup.
Conversational Threads
We'd like for the bot to do more than just say "meow". But how do you deploy a new version of something running on Cloud Run?
Change directory into cloud-slack-bot/extra-credit
:
cd ../extra-credit/
Open the current folder in Cloud Shell Editor:
cloudshell workspace .
Botkit offers the ability to handle conversations. With these, the bot can request more information and react to messages beyond a one word reply.
Define the dialog
First, see how conversational functions are defined at the end of the file:
// ...
const maxCats = 20;
const catEmojis = [
':smile_cat:',
':smiley_cat:',
':joy_cat:',
':heart_eyes_cat:',
':smirk_cat:',
':kissing_cat:',
':scream_cat:',
':crying_cat_face:',
':pouting_cat:',
':cat:',
':cat2:',
':leopard:',
':lion_face:',
':tiger:',
':tiger2:',
];
/**
* Function to concatenate cat emojis
* @param {number} numCats Number of cat emojis.
* @return {string} The string message of cat emojis.
*/
function makeCatMessage(numCats) {
let catMessage = '';
for (let i = 0; i < numCats; i++) {
// Append a random cat from the list
catMessage += catEmojis[Math.floor(Math.random() * catEmojis.length)];
}
return catMessage;
}
/**
* Function to create the kitten conversation
* @param {Object} controller The botkit controller.
* @return {Object} The BotkitConversation object.
*/
function createKittenDialog(controller) {
const convo = new BotkitConversation('kitten-delivery', controller);
convo.ask('Does someone need a kitten delivery?', [
{
pattern: 'yes',
handler: async (response, convo, bot) => {
await convo.gotoThread('yes_kittens');
},
},
{
pattern: 'no',
handler: async (response, convo, bot) => {
await convo.gotoThread('no_kittens');
},
},
{
default: true,
handler: async (response, convo, bot) => {
await convo.gotoThread('default');
},
},
]);
convo.addQuestion(
'How many would you like?',
[
{
pattern: '^[0-9]+?',
handler: async (response, convo, bot, message) => {
const numCats = parseInt(response);
if (numCats > maxCats) {
await convo.gotoThread('too_many');
} else {
convo.setVar('full_cat_message', makeCatMessage(numCats));
await convo.gotoThread('cat_message');
}
},
},
{
default: true,
handler: async (response, convo, bot, message) => {
if (response) {
await convo.gotoThread('ask_again');
} else {
// The response '0' is interpreted as null
await convo.gotoThread('zero_kittens');
}
},
},
],
'num_kittens',
'yes_kittens'
);
// If numCats is too large, jump to start of the yes_kittens thread
convo.addMessage(
'Sorry, {{vars.num_kittens}} is too many cats. Pick a smaller number.',
'too_many'
);
convo.addAction('yes_kittens', 'too_many');
// If response is not a number, jump to start of the yes_kittens thread
convo.addMessage("Sorry I didn't understand that", 'ask_again');
convo.addAction('yes_kittens', 'ask_again');
// If numCats is 0, send a dog instead
convo.addMessage(
{
text:
'Sorry to hear you want zero kittens. ' +
'Here is a dog, instead. :dog:',
attachments: [
{
fallback: 'Chihuahua Bubbles - https://youtu.be/s84dBopsIe4',
text: '<https://youtu.be/s84dBopsIe4|' + 'Chihuahua Bubbles>!',
},
],
},
'zero_kittens'
);
// Send cat message
convo.addMessage('{{vars.full_cat_message}}', 'cat_message');
convo.addMessage('Perhaps later.', 'no_kittens');
return convo;
}
This new conversation directs the thread based on the responses. For example, if the user responds "no" to the kitten question, it jumps to the message labeled "no_kittens", which is the end of that conversational thread.
Adding the dialog to the controller
Now that the conversation is defined, see how to add it to the controller:
async function kittenbotInit() {
// ...
const controller = new Botkit({
webhook_uri: '/api/messages',
adapter: adapter,
});
// Add Kitten Dialog
const convo = createKittenDialog(controller);
controller.addDialog(convo);
// Controller is ready
controller.ready(() => {
// ...
});
}
Trigger the dialog
Now that dialog is available for the controller to use, see how the conversation gets started when the chatbot hears "kitten", "kittens", "cat", or "cats":
// ...
controller.ready(() => {
controller.hears(
['hello', 'hi', 'hey'],
['message', 'direct_message'],
async (bot, message) => {
await bot.reply(message, 'Meow. :smile_cat:');
return;
}
);
// START: listen for cat emoji delivery
controller.hears(
['cat', 'cats', 'kitten', 'kittens'],
['message', 'direct_message'],
async (bot, message) => {
// Don't respond to self
if (message.bot_id !== message.user) {
await bot.startConversationInChannel(message.channel, message.user);
await bot.beginDialog('kitten-delivery');
return;
}
}
);
// END: listen for cat emoji delivery
// ...
});
// ...
Update the app
Re-deploy the application to Cloud Run:
gcloud run deploy kittenbot \ --source . \ --platform managed \ --region $REGION \ --set-env-vars PROJECT_ID=$PROJECT_ID \ --allow-unauthenticated
Try it out
Congratulations! You just updated a Slack bot running on Cloud Run to a new version.
Slash Commands
What if you don't want to have a conversation with the user? What if you'd prefer to simply trigger an action with one simple command?
Slack offers this functionality via Slash commands, which allow users to invoke your application by entering the command into the message box.
Enable Slack Slash Commands
- Go to the Slash Commands section under Features on your Apps Management Page.
- Click Create New Command.
- Configure a
/cats
command with your kittenbot service URL. Remember to use the same endpoint you used to enable the Events API! This is your URL, plus'/api/messages'
.
- Follow the prompt to update your app and permissions.
Add Slash Commands to your Controller
See how a handler for slash commands was added inside the controller.ready function:
// ...
// Controller is ready
controller.ready(() => {
// ...
// START: slash commands
controller.on('slash_command', async (bot, message) => {
const numCats = parseInt(message.text);
const response = makeCatMessage(numCats);
bot.httpBody({ text: response });
});
// END: slash commands
});
// ...
Try it out
Enter /cats plus a number to send the slash command. Eg: /cats 8
The bot will respond with 8 cats, only visible to you:
12. Cleanup
Congratulations, you now have a Slack bot running on Cloud Run. Time for some cleaning of the resources used (to save on cost and to be a good cloud citizen).
Delete the project
You can delete the entire project, directly from Cloud Shell:
gcloud projects delete $PROJECT_ID
Alternatively, if you prefer to delete the different resources one by one, proceed to the next section.
Delete the deployment
gcloud run services delete kittenbot --region $REGION
Command output
Service [kittenbot] will be deleted. Do you want to continue (Y/n)? y Deleted service [kittenbot].
Delete your client signing secret
gcloud secrets delete client-signing-secret
Command output
You are about to destroy the secret [client-signing-secret] and its [1] version(s). This action cannot be reversed. Do you want to continue (Y/n)? y Deleted secret [client-signing-secret].
Delete your bot token secret
gcloud secrets delete bot-token
Command output
You are about to destroy the secret [bot-token] and its [1] version(s). This action cannot be reversed. Do you want to continue (Y/n)? y Deleted secret [bot-token].
Delete the storage buckets
First, list the Google Cloud Storage buckets to get the bucket path:
gsutil ls
Command output
gs://[REGION.]artifacts.<PROJECT_ID>.appspot.com/ gs://<PROJECT_ID>_cloudbuild/
Now, delete the artifacts bucket:
gsutil rm -r gs://[REGION.]artifacts.${PROJECT_ID}.appspot.com/
Command output
Removing gs://[REGION.]artifacts.<PROJECT_ID>.appspot.com/...
Finally, delete the cloudbuild bucket:
gsutil rm -r gs://${PROJECT_ID}_cloudbuild/
Command output
Removing gs://<PROJECT_ID>_cloudbuild/...
13. Congratulations!
You now know how to run a Slack bot on Cloud Run!
We've only scratched the surface of this technology and we encourage you to explore further with your own Cloud Run deployments.
What we've covered
- Creating a bot custom integration in Slack
- Securing your Slack secrets with Secret Manager
- Deploying your Slack bot on Cloud Run
Next Steps
- Complete more Cloud Run tutorials
Learn More
- Check out the other Google Cloud Slack integration examples on GitHub.
- Try out other Google Cloud features for yourself. Have a look at our tutorials.