X
    Categories: API Tutorials and Use Cases

How to Create a Slack Chatbot and Connect It With Cloudways API

In the series of Cloudways API related use cases, several important uses of the API (such as purging Varnish cache, adding and deleting Application, and managing Github repos) have been discussed.

In today’s article, I will create a Slack chatBot and connect it with Cloudways API. This bot will perform the following server operations:

  • Get servers from Cloudways.
  • Get server application from Cloudways.
  • Start, stop, restart, and delete a server.
  • Restart server services (Apache, Nginx, PHP-FPM, etc.).
  • Manage Varnish cache.

In the future iterations of this chatbot, I will integrate more operations and functionalities.

Complete code can be found at CloudChatbot repository.

Create a Slackbot

Log in to your Slack group and click on the gear icon. Click on Add an app or integration.

Now, on the apps page, click on Build button located in the top right corner.

Now on the Build page, click on Make a Custom Integration.

On the next page, click on Bots.

Name the Bot and click Add bot integration.

Once the process finishes, the bot has been created. This bot could now be added to Channels on the Slack group.

For now, copy the API Token of the bot and copy it some place safe.

Next, start working on chatbot script. But before that, I will add several functions in `class CloudwaysAPIClient`. This class was earlier created in a previous article on Cloudways API.

Updating CloudwaysAPIClient Class

Now open CloudwaysAPIClient.php and add the following functions in it to manage a server and its services:

public  function get_services($serverid)
{
	try{
		$url = self::API_URL . "/service";
		$data = ['server_id' => $serverid];
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$response = $this->client->get($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}
public  function manage_services($serverid,$service,$state)
{
	try{
		$url = self::API_URL . "/service/state";
		$data = ['server_id' => $serverid,
				 'service' => $service,
				 'state' => $state
				];
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$response = $this->client->post($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}
public  function start_server($serverid)
{
	try{
		$url = self::API_URL . "/server/start";
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$data = ['server_id' => $serverid,
				];
		$response = $this->client->post($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}
public  function stop_server($serverid)
{
	try{
		$url = self::API_URL . "/server/stop";
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$data = ['server_id' => $serverid,
				];
		$response = $this->client->post($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}
public  function restart_server($serverid)
{
	try{
		$url = self::API_URL . "/server/restart";
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$data = ['server_id' => $serverid,
				];
		$response = $this->client->post($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}
public  function delete_server($serverid)
{
	try{
		$url = self::API_URL . "/server/$serverid";
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$response = $this->client->delete($url, array('headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}

Let’s understand the above functions:

  • Function get_services gets the status of all services including Apache, Varnish, Nginx, PHP-FPM running on the server.
  • Function `manage_services` manages different services of the server and helps restart services.
  • Functions `start_server` ,`restart_server`,`stop_server`,`delete_server` carry out various server management functions.

Next is the creation of the function to manage Varnish services for the servers. Paste the following code inside the class CloudwaysAPIClient in the CloudwaysAPIClient.php file:

public  function service_varnish($serverid,$action)
{
	try{
		$actions = ['enable', 'disable', 'purge'];
		$url = self::API_URL . "/service/varnish";
		$data = null;
		if (in_array($action, $actions)) {
			$data = ['server_id' => $serverid,
					 'action' => $action
					];
		}
		$header = array('Authorization'=>'Bearer ' . $this->accessToken);
		$response = $this->client->post($url, array('query' => $data,'headers' => $header));
		return json_decode($response->getBody()->getContents());
	} catch (RequestException $e) {
		$response = $this->StatusCodeHandling($e);
		return $response;
	}
}

The function service_varnish will enable, disable and purge Varnish cache. All the required functions in class CloudwaysAPIClient have been now created.

Creating Slackbot Script

Create a new folder in public_html and name it Slackbot.

I will first install a PHP SDK to work with Slack API using Composer. For this, create a new file composer.json and paste the following line in it:

{  
   "minimum-stability":"dev",
   "require":{  
      "jclg/php-slack-bot":"dev-master"
   }
}

Now run composer install. Once the process finishes, copy and paste the updated CloudwaysAPIClient.php in this folder. Then, create a new file mybot.php and paste the following code in it:

require 'vendor/autoload.php';
include 'cloudwaysapi.php';
use PhpSlackBot\Bot;
// Custom command
class MyBot extends \PhpSlackBot\Command\BaseCommand {
	protected function configure() {
		$this->setName("cloud");
	}
	protected function execute($message, $context) {
		$args = $this->getArgs($message);
		$command = isset($args[1]) ? $args[1] : '';
		$api_key = '<Your API KEY>';
		$email = '<Your Email Address>';
		$cw_api = new CloudwaysAPI($email,$api_key);
		switch ($command) {
			case "hello":
			$username = $this->getUserNameFromUserId($this->getCurrentUser());
			$msg= "Hello @".$username."!\nFollowing are the commands that I can perform for now:
                               cloud hello
                               To List server type 'cloud getservers'
                               To Get server services running type 'cloud getservices <serverid>'
                               To Restart service type 'cloud restartservice (mysql|apache2|nginx|memcached|varnish|redis-server|php5-fpm|elasticsearch) <serverid>'
                               To Manage varnish type 'cloud varnish (purge|enable|disable) (all|<serverid>)'
                               To Manage server type 'cloud server (start|stop|restart|delete) <serverid>'
                               ";
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
			default:
			$msg= "valid commands are:
                               cloud hello
                               cloud getservers
                               cloud getservices <serverid>
                               cloud restart (mysql|apache2|nginx|memcached|varnish|redis-server|php5-fpm|elasticsearch) <serverid>
                               cloud varnish (purge|enable|disable) (all|<serverid>)
                               cloud server (start|stop|restart|delete) <serverid>
                               ";
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
		}
	}
	private function getArgs($message) {
		$args = array();
		if (isset($message['text'])) {
			$args = array_values(array_filter(explode(' ', $message['text'])));
		}
		$commandName = $this->getName();
		// Remove args which are before the command name
		$finalArgs = array();
		$remove = true;
		foreach ($args as $arg) {
			if ($commandName == $arg) {
				$remove = false;
			}
			if (!$remove) {
				$finalArgs[] = $arg;
			}
		}
		return $finalArgs;
	}
}

Let’s understand the above functions:

  • Function configure() sets the command name for the chatbot.
  • Function getArgs() breaks the message sent from the Slack channel into an array to perform the required action.
  • The message from the Slack channel to Slackbot will be executed by the function execute(). In this function, I have created cases to perform valid actions sent to the chatbot. To begin, I have created a single case. Thus, whenever a user sends “cloud hello, the bot will return all the chat commands to the user.

Let’s give it a try. Create a new file index.php in the same folder and paste the following code in it.

<?php
require 'vendor/autoload.php';
require 'mybot.php';
use PhpSlackBot\Bot;
$bot = new Bot();
$bot->setToken('Add Your Slack Bot Token Here’'); // Get your token here https://my.slack.com/services/new/bot
$bot->loadCommand(new MyBot());
$bot->run();

In above code, I have created an object of $bot using PHP SDK, bound the token and the newly created command class to it, and then run it.

Now, go to the Slack channel and send a direct message to your bot. You will notice that the bot is offline.

Let’s take the bot online. Now run php index.php through the command line. The result is the following message:

Now, go back to the Slack channel, and you will notice that the bot is online.

Type “cloud hello” and press Enter. You will get the following response:

If you get the above message, the bot has been configured correctly!

It is now time to add cases for getting servers and the available applications. Now open mybot.php and add the following code inside switch and then save the file:

case "getservers":
$servers = $cw_api->get_servers();
$msg = null;
foreach($servers->servers as $server) {
	$msg = "ServerID: ".$server->id." Name:".$server->label." Status: " .$server->status ." Total Apps: " . sizeof($server->apps) . "\n";
}
$this->send($this->getCurrentChannel(), null, $msg);
break;
case "getapplication":
$serverid = isset($args[2]) ? $args[2] : '';
$servers = $cw_api->get_servers();
if(sizeof($args) == 3){
	if($serverid === "all"){
		foreach($servers->servers as $server) {
			foreach ($server->apps as $app) {
				if(empty($msg))
				{
					$msg = "Server: ".$server->label." ApplicationID:".$app->id." ApplicationLabel:".$app->label. "\n";
				}
				$msg .= "Server: ".$server->label." ApplicationID:".$app->id." ApplicationLabel:".$app->label. "\n";
			}
		}
	}
	else{
		foreach($servers->servers as $server) {
			if($server->id == $serverid){
				foreach ($server->apps as $app) {
					if(empty($msg))
					{
						$msg = "Applications for Server: " . $server->label . " are:\n";
					}
					$msg .= "ApplicationID:".$app->id." ApplicationLabel:".$app->label . "\n";
				}
			}
		}
	}
	$this->send($this->getCurrentChannel(), null, $msg);
}
else{
	$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Note: Missing Arguments For The Following Operation");
}
break;

Let’s understand the above code:

  • When the user types cloud getserver, case getserver will be activated and the chatbot will list all the servers along with status and the total number of applications.
  • There are two scenarios for getapplications. If the user wants to get all the applications on all the servers, use cloud getapplications all. If the user requires applications for a specific server, use cloud getapplications <serverid>. The placeholder <serverid> should be replaced with your server id.

Let’s test this functionality.

First, stop all instances of index.php using Ctrl+C. Launch the script again. Send these new messages to your bot and see the magic.

In the following image, you can see how many apps are running inside the Server 59727.

Now, you can see the names of all applications inside your Cloudways account.

In the following image, the chatbot informed me about all the application inside the Server 59727.

Next, create cases to get server services and then manage these services.

Paste the following case inside switch and save the file:

case "getservices":
$serverid = isset($args[2]) ? $args[2] : '';
$servers = $cw_api->get_services($serverid);
$msg = null;
foreach ($servers->services->status as $key => $value) {
	if($value === 0){
		$value = "stopped";
	}
	if($value === 1){
		$value = "running";
	}
	if(empty($msg)){
		$msg = "\n".$key .": ".$value."\n";
	}else{
		$msg .= $key .": ".$value."\n";
	}
}
$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
break;
case "restartservice":
if(sizeof($args) == 4){
	$service = isset($args[2]) ? $args[2] : '';
	$serverid = isset($args[3]) ? $args[3] : '';
	$servers = $cw_api->manage_services($serverid,$service,"restart");
	$msg = "$service is restarted";
	$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
}else{
	$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Note: All the parameters are required");
}
break;

Let’s understand the above code.

  • When the user types cloud getservices <serverid> (where <serverid> is to be replaced by the server id) case getservices will be activated and send all the services of the server to the
  • When the user types cloud restartservice <servicename> <serverid> (where <serverid> is to be replaced by the server id and <servicename> is to be replaced by apache2, mysql, nginx, etc.) case restartservice will restart the required server service.

Like before, stop the script and rerun it. Carry the following chat with the bot.

Now, create cases for starting, stopping, restarting, and deleting servers. Paste the following code inside switch and then save the file:

case "server":
if(sizeof($args) == 4){
	$service = isset($args[2]) ? $args[2] : '';
	$serverid = isset($args[3]) ? $args[3] : '';
	$msg = "Server is $service";
	if($service == "delete"){
		$servers = $cw_api->delete_server($serverid);
		$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
	}elseif($service == "start"){
		$servers = $cw_api->start_server($serverid);
		$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
	}elseif($service == "stop"){
		$servers = $cw_api->stop_server($serverid);
		$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
	}elseif($service == "restart"){
		$servers = $cw_api->restart_server($serverid);
		$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
	}else{
		$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "unknown command");
	}
}else{
	$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Note: All the parameters are required");
}
break;

When user type cloud server <service> <serverid> where `<service>` is to be replaced with start|stop|restart|delete and <serverid> is to be replaced with the server id.

Before performing these actions, you need to make sure that you are not running the bot on the same server for which you are performing the action. Otherwise, the bot will go offline once the operation finishes.

Now, I will create cases to manage Varnish cache. Paste the following code inside switch and save the file:

case "varnish":
if(sizeof($args) == 4){
	$service = isset($args[2]) ? $args[2] : '';
	$serverid = isset($args[3]) ? $args[3] : '';
	if($serverid === "all"){
		$servers = $cw_api->get_servers();
		foreach($servers->servers as $server) {
			$result = $cw_api->service_varnish($server->id, $service);
			if(isset($result->response))
			{
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Varnish $service of all server");
			}else{
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), var_dump($result));
			}
		}
	}
	else{
		$result = $cw_api->service_varnish($serverid, $service);
		if(isset($result->response))
		{
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Varnish $service for server");
		}else{
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), var_dump($result));
		}
	}
}else{
	$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Not All the parameters are required");
}
break;

When the user types cloud varnish <action> <serverid> where <action> is to be replaced with enable|disable|purge and `<serverid>` is to be replaced with the actual server id or with “all” in order to disable for all servers. The bot will perform the case varnish, and the required action will be performed on server.

Stop index.php and rerun it. Send Varnish and you will see:

You can run cloud getservices <serverid> to verify whether the varnish is disabled.

Here is the complete code for the class Mybot:

<?php
require 'vendor/autoload.php';
include 'cloudwaysapi.php';
use PhpSlackBot\Bot;
// Custom command
class MyBot extends \PhpSlackBot\Command\BaseCommand {
	protected function configure() {
		$this->setName("cloud");
	}
	protected function execute($message, $context) {
		$args = $this->getArgs($message);
		$command = isset($args[1]) ? $args[1] : '';
		$api_key = 'gR1YywOMN2gG8L0FZC6Rd3QSsr0jlM';
		$email = 'ahmed.khan@cloudways.com';
		$cw_api = new CloudwaysAPI($email,$api_key);
		switch ($command) {
			case "getservers":
			$servers = $cw_api->get_servers();
			$msg = null;
			foreach($servers->servers as $server) {
				$msg = "ServerID: ".$server->id." Name:".$server->label." Status: " .$server->label ." Total Apps: " . sizeof($server->apps) . "\n";                
			}
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
			case "getapplication":
			$serverid = isset($args[2]) ? $args[2] : '';
			$servers = $cw_api->get_servers();
			if($serverid === "all"){
				foreach($servers->servers as $server) {
					foreach ($server->apps as $app) {
						if(empty($msg))
						{
							$msg = "Server: ".$server->label." ApplicationID:".$app->id." ApplicationLabel:".$app->label. "\n";
						}
						$msg .= "Server: ".$server->label." ApplicationID:".$app->id." ApplicationLabel:".$app->label. "\n";
					}
				}
			}
			else{
				foreach($servers->servers as $server) {
					if($server->id == $serverid){
						foreach ($server->apps as $app) {
							if(empty($msg))
							{
								$msg = "Applications for Server: " . $server->label . "are:\n";
							}
							$msg .= "Server: ".$server->label." ApplicationID:".$app->id." ApplicationLabel:".$app->label . "\n";
						}
					}
				}
			}
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
			case "hello":
			$msg= "\nHello!\nFollowing are the commands that I can perform for now:
1. cloud hello
2. To List server type 'cloud getservers'
3. To Get server services running type 'cloud getservices <serverid>'
4. To Restart service type 'cloud restartservice (mysql|apache2|nginx|memcached|varnish|redis-server|php5-fpm|elasticsearch) <serverid>'
5. To Manage varnish type 'cloud varnish (purge|enable|disable) (all|<serverid>)'
6. To Manage server type 'cloud server (start|stop|restart|delete) <serverid>'
";
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
			case "restartserver":
			foreach($servers->servers as $server) {
				$result = $cw_api->service_varnish($server->id, 'purge');
				if(isset($result->response))
				{
					$msg = "SERVER ->". $server->label.", Status cache purged";
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Varnish Purge");
				}else{
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), var_dump($result));
				}
			}    
			break; 
			case "getservices":
			$arg = isset($args[2]) ? $args[2] : '';
			$servers = $cw_api->get_services($arg);
			$msg = null;
			foreach ($servers->services->status as $key => $value) {
				if($value === 0){
					$value = "stopped";
				}
				if($value === 1){
					$value = "running";
				}
				if(empty($msg)){
					$msg = "\n".$key .": ".$value."\n";
				}else{
					$msg .= $key .": ".$value."\n";
				}
			}
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
			case "restartservice":
			if(sizeof($args) == 4){
				$service = isset($args[2]) ? $args[2] : '';
				$serverid = isset($args[3]) ? $args[3] : '';
				$servers = $cw_api->manage_services($serverid,$service,"restart");
				$msg = "$service is restarted";
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			}else{
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Note: All the parameters are required");
			}            
			break;
			case "varnish":
			if(sizeof($args) == 4){
				$service = isset($args[2]) ? $args[2] : '';
				$serverid = isset($args[3]) ? $args[3] : '';
				if($serverid === "all"){
					$servers = $cw_api->get_servers();
					foreach($servers->servers as $server) {
						$result = $cw_api->service_varnish($server->id, $service);
						if(isset($result->response))
						{
							$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Varnish $service of all server");
						}else{
							$this->send($this->getCurrentChannel(), $this->getCurrentUser(), var_dump($result));
						}
					} 
				}
				else{
					$result = $cw_api->service_varnish($serverid, $service);
					if(isset($result->response))
					{
						$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Varnish $service for server");
					}else{
						$this->send($this->getCurrentChannel(), $this->getCurrentUser(), var_dump($result));
					}
				}
			}else{
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Not All the parameters are required");
			}            
			break;
			case "server":
			if(sizeof($args) == 4){
				$service = isset($args[2]) ? $args[2] : '';
				$serverid = isset($args[3]) ? $args[3] : '';
				$msg = "Server is $service";
				if($service == "delete"){
					$servers = $cw_api->delete_server($serverid);
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
				}elseif($service == "start"){
					$servers = $cw_api->start_server($serverid);
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
				}elseif($service == "stop"){
					$servers = $cw_api->stop_server($serverid);
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
				}elseif($service == "restart"){
					$servers = $cw_api->restart_server($serverid);
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
				}else{
					$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "unknown command");
				}
			}else{
				$this->send($this->getCurrentChannel(), $this->getCurrentUser(), "Note: All the parameters are required");
			}            
			break;
			default:
			$msg= "valid commands are:
1. cloud hello
2. cloud getservers
3. cloud getservices <serverid>
4. cloud restart (mysql|apache2|nginx|memcached|varnish|redis-server|php5-fpm|elasticsearch) <serverid>
5. cloud varnish (purge|enable|disable) (all|<serverid>)
6. cloud server (start|stop|restart|delete) <serverid>
";
			$this->send($this->getCurrentChannel(), $this->getCurrentUser(), $msg);
			break;
		}
	}
	private function getArgs($message) {
		$args = array();
		if (isset($message['text'])) {
			$args = array_values(array_filter(explode(' ', $message['text'])));
		}
		$commandName = $this->getName();
		// Remove args which are before the command name
		$finalArgs = array();
		$remove = true;
		foreach ($args as $arg) {
			if ($commandName == $arg) {
				$remove = false;
			}
			if (!$remove) {
				$finalArgs[] = $arg;
			}
		}
		return $finalArgs;
	}
}
?>

The chatbot is fully operational and it responds to all the predetermined commands. However, you might notice that  once you close the terminal, the bot goes offline. Thus, in order to keep the bot running, make sure that index.php runs continuously. In order to run the script continuously, type the following command in the SSH terminal:

$ nohup php index.php &

The above command adds a process in the background of the server. Now, even when the terminal is closed, the bot will continue to be online.

Conclusion

In this tutorial, I discussed how to create a Slack chatbot and then connect it with Cloudways API to perform server operation directly from the Slack chat. I hope that you have been able to follow the code without any issues. If you have a question or would like to add to the discussion, please leave a comment below.

Ahmed Khan: Ahmed was a PHP community expert at Cloudways - A Managed PHP Hosting Cloud Platform. He is a software engineer with extensive knowledge in PHP and SEO. He loves watching Game of Thrones is his free time. Follow Ahmed on Twitter to stay updated with his works. You can email him at ahmed.khan@cloudways.com