Sending Message to Mattermost With PHP and Github Actions

Β·

8 min read

Today I show you how you can send notifications to Mattermost with PHP and Github Actions. There are some implementations on Github Marketplace but nothing was made with PHP, there was Javascript, Go, or Python. But PHP can be used to create command-line utilities and it's very easy. Don't trust me? Come I'll show you.

Creating new project

First of all, create a new folder for your project, mine is action-mattermost-notify. You can do this with the command as follows.

mkdir action-mattermost-notify

Now initializae new repository

# cd action-mattermost-notify
composer init

The composer will ask you for some information such as project name, author name, and email. When you are done you will have a folder structure like this one:

action-mattermost-notify
β”œβ”€β”€ composer.json
β”œβ”€β”€ composer.lock
β”œβ”€β”€ src
└── vendor

Now You need to add some dependencies. (Here are two of them). The first one is the HTTP client and the second one is Console.

# cd action-mattermost-notify
composer require symfony/http-client
composer require symfony/console

After this step, 1st part is done. Go to next step.

Creating Console application

With the Console component is creating a new console application easy. When you finish this part your folder structure will look like follows

action-mattermost-notify
β”œβ”€β”€ app.php # Added this one
β”œβ”€β”€ composer.json
β”œβ”€β”€ composer.lock
β”œβ”€β”€ src
β”‚   └── SendCommand.php # Added this one
└── vendor

First create app.php, which is the main file that launches your console application, and put there following content.

# app.php
require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Application;

# Load version information from the composer file
# You will need to add a version tag to your composer.json
$version = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);

$app = new Application('Action Mattermost Notify', $version['version']);

$app->run();

Good job! Now try to run it

php app.php
Action Mattermost Notify 1.0.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  completion  Dump the shell completion script
  help        Display help for a command
  list        List commands

Next, you need to create a new command that will communicate with Mattermost. Go into the src folder and create SendCommand.php

# cd src
touch SendCommand.php

...and put there following content

<?php
declare(strict_types=1);

# Change namespace to your project's namespace
namespace Maymeow\ActionMattermostNotify;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpClient\HttpClient;

class SendCommand extends Command
{
    protected static $defaultName = 'send';

    /**
     * Configure method
     *
     * @return void
     */
    public function configure(): void
    {
        $this
            ->setDescription('Send a message to Mattermost')
            ->setHelp('This command allows you to send a message to Mattermost');

        $this
            ->addArgument('message', InputArgument::REQUIRED, 'The message to send')
            ->addOption('channel', null, InputOption::VALUE_OPTIONAL, 'The channel to send the message to')
            ->addOption('username', null, InputOption::VALUE_OPTIONAL, 'The username to send the message as')
            ->addOption('icon', null, InputOption::VALUE_OPTIONAL, 'The icon to send the message with')
            ->addOption('url', null, InputOption::VALUE_OPTIONAL, 'The URL to send the message with');
    }

    /**
     * Execute method
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input Input interface
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output interface
     * @return int
     */
    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $message = $input->getArgument('message');
        $channel = $input->getOption('channel');
        $username = $input->getOption('username');
        $icon = $input->getOption('icon');
        $url = $input->getOption('url');

        $client = HttpClient::create();

        $response = $client->request('POST', $url, [
            'body' => json_encode([
                'channel' => $channel,
                'text' => $message,
                'username' => $username,
                'icon_url' => $icon,
            ]),
            'headers' => [
                'Content-Type' => 'application/json',
            ],
        ]);

        $output->writeln($response->getContent());

        return Command::SUCCESS;
    }
}

Let me show you what are parts of this file doing

public function configure(): void

The function above, as the name says, is used to configure your command. It tells your application what argument or options this command needs, its name, and description or you can modify the handler (how you call it from command line).

public function execute(InputInterface $input, OutputInterface $output): int

Function execute is the one where all magic happens. It is used to execute your actions.

The part below is HTTP Client that calls your webhook with the POST method and sends there required information.

// SendCommand::execute()
//...
$client = HttpClient::create();
$response = $client->request('POST', $url, [
    'body' => json_encode([
        'channel' => $channel,
        'text' => $message,
        'username' => $username,
        'icon_url' => $icon,
    ]),
    'headers' => [
        'Content-Type' => 'application/json',
    ],
]);
// ...

Ok, Now register your Command to your application with $app->add(new SendCommand());. Add this to your app.php file. After this step, your file will look like this one

<?php

require __DIR__ . '/vendor/autoload.php';

use Maymeow\ActionMattermostNotify\SendCommand;
use Symfony\Component\Console\Application;

$c = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);

$app = new Application('Action Mattermost Notify', $c['version']);

$app->add(new SendCommand());

$app->run();

Try to run your app again php app.php. In your response another action has been added:

Action Mattermost Notify 1.0.0
# // ...
Available commands:
  completion  Dump the shell completion script
  help        Display help for a command
  list        List commands # <--- This one as beed added

To see information about any command append --help behind that command. For example send command

php app.php send --help

Required are the message and url of your webhook.

You can obtain the webhook URL on your mattermost instance in integrations, then click on Incoming webhook then add new and provide requested information.

Sending message

When you have all you need (created webhook), you can send a meesage to the server as follows

php app.php send "Hello World from PHP commandline!" --url "https://your-mattermost.url/webhook-id"

Cool isn't it? But, I like it if I can call the application without php word as follows

`./action-mattermost-notify send "Hello World from PHP commandline!" --url "https://your-mattermost.url/webhook-id"`

Ok, Let's do this.

Packing your application to single file

In this step, you will learn how to pack your PHP application into phar. You have 2 options, read the PHP manual and do it your way, or IMO a more elegant way to use phar-composer.

First of all, you need to install it.

wget https://github.com/clue/phar-composer/releases/download/v1.4.0/phar-composer-1.4.0.phar \
&& chmod +x phar-composer-1.4.0.phar \
&& mv phar-composer-1.4.0.phar /usr/local/bin/phar-composer

To check the current version go to the releases page.

Before you can pack your app you need to make small changes in your composer file. Add "bin": ["app.php"], somewhere in composer file. This tells to phar-composer which file needs to call when execute.

Ok build it and make it executable.

phar-composer build
chmod +x action-mattermost-notify.phar

After this you can call it like follows

./action-mattermost-notify.phar

Ok, the console application is finished and now you can create a GitHub action

Creating Action

The folder structure after this part is finished will look like follows

action-mattermost-notify
β”œβ”€β”€ action.yml # Added this one
β”œβ”€β”€ app.php
β”œβ”€β”€ composer.json
β”œβ”€β”€ composer.lock
β”œβ”€β”€ Dockerfile # Added this one
β”œβ”€β”€ entrypoint.sh # Added this one
β”œβ”€β”€ src
β”‚   └── SendCommand.php
└── vendor

First of all, you need to automate the steps above like building phar and making it executable. You don't want to have bin files in your git repository. To do this, we use Docker in this tutorial. Create Dockerfile and put there following content:

# Dockerfile
# Folloing image has composer-phar preinstaled in it
FROM ghcr.io/maymeow/php-ci-cd/php-ci-cd:8.1.6-cs-git-psalm AS build-env

WORKDIR /app

COPY . /app

RUN composer install --no-ansi --no-dev --no-interaction --no-plugins --no-progress --optimize-autoloader --no-scripts

RUN phar-composer build && chmod +x action-mattermost-notify.phar

# Use smallest php image
FROM php:8.1.6-cli-alpine

COPY --from=build-env /app/action-mattermost-notify.phar /usr/local/bin/action-mattermost-notify

COPY --from=build-env /app/entrypoint.sh /entrypoint.sh

RUN action-mattermost-notify list

ENTRYPOINT ["/entrypoint.sh"]

The example above is a multistep build. In the production image, you will only have phar without source codes.

The second file you need to add is entrypoint.sh which runs when the image starts. Create it with the following content

#entrypoint.sh
#!/bin/sh

action-mattermost-notify send "$1" --url "$2" --channel "$3" --username "$5" --icon "$4"

At last, you need to create an action configuration file that tells Github all the required information about the action that you creating. It is called action.yml and has to be in the root directory. Create it with the following content:

# action.yml
name: 'Action Name' #change this to your action name
author: 'Action Author' # Change this to your
description: 'Action Description' # change this
branding:
  icon: 'command'
  color: 'purple'
inputs: # following are all inputs that can be used in this action
  message:
    description: 'Enter the message to send to Mattermost'
    required: true
  url:
    description: 'The URL to send the message to'
    required: true
  channel:
    description: 'Enter the channel to send the message to'
    required: false
  icon:
    description: 'Enter the icon to use for the message'
    required: false
  username:
    description: 'Enter the username to use for the message'
    required: false
runs: # How this action start? 
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.message }}
    - ${{ inputs.url }}
    - ${{ inputs.channel }}
    - ${{ inputs.icon }}
    - ${{ inputs.username }}

Good job! Congratulation, You are reading this to the finish. You now have your own Github action and you learned how to

  • create a PHP console application
  • how to pack it in a single Phar file
  • how to create Github Action with PHP

How to run it

At the very end, ill show you how you can use it. There are more options on how to call it.

  • Register it in the marketplace (via new release)
  • Call it with branch name or commit hash

If you have published it you can call it as follows

- name: Action Mattermost Notify
uses: MayMeow/action-mattermost-notify@v1 # Change this to your action
with:
  url: ${{ secrets.MATTERMOST_WEBHOOK }}
  message: "Hello world from ${{ github.repository }}"

Your action has name your-github-username/your-action-repository@version or your-github-username/your-action-repository@branch-name or your-github-username/your-action-repository@commit-hash. The last two options don't require to have action registered in the marketplace.

In the end

All source code is available on my Github: Action Mattermost notify and Mattermost Action example.

Thank you for reading this post and I hope you enjoy it.

Originally published at My Blog

Β