May's Blog

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.

Photo by Daria Nepriakhina πŸ‡ΊπŸ‡¦ on Unsplash

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.

1mkdir action-mattermost-notify

Now initializae new repository

1# cd action-mattermost-notify
2composer 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:

1action-mattermost-notify
2β”œβ”€β”€ composer.json
3β”œβ”€β”€ composer.lock
4β”œβ”€β”€ src
5└── 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.

1# cd action-mattermost-notify
2composer require symfony/http-client
3composer 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

1action-mattermost-notify
2β”œβ”€β”€ app.php # Added this one
3β”œβ”€β”€ composer.json
4β”œβ”€β”€ composer.lock
5β”œβ”€β”€ src
6β”‚Β Β  └── SendCommand.php # Added this one
7└── vendor

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

 1# app.php
 2require __DIR__ . '/vendor/autoload.php';
 3
 4use Symfony\Component\Console\Application;
 5
 6# Load version information from the composer file
 7# You will need to add a version tag to your composer.json
 8$version = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
 9
10$app = new Application('Action Mattermost Notify', $version['version']);
11
12$app->run();

Good job! Now try to run it

1php app.php
 1Action Mattermost Notify 1.0.0
 2
 3Usage:
 4  command [options] [arguments]
 5
 6Options:
 7  -h, --help            Display help for the given command. When no command is given display help for the list command
 8  -q, --quiet           Do not output any message
 9  -V, --version         Display this application version
10      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
11  -n, --no-interaction  Do not ask any interactive question
12  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
13
14Available commands:
15  completion  Dump the shell completion script
16  help        Display help for a command
17  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

1# cd src
2touch SendCommand.php

…and put there following content

 1<?php
 2declare(strict_types=1);
 3
 4# Change namespace to your project's namespace
 5namespace Maymeow\ActionMattermostNotify;
 6
 7use Symfony\Component\Console\Command\Command;
 8use Symfony\Component\Console\Input\InputArgument;
 9use Symfony\Component\Console\Input\InputInterface;
10use Symfony\Component\Console\Input\InputOption;
11use Symfony\Component\Console\Output\OutputInterface;
12use Symfony\Component\HttpClient\HttpClient;
13
14class SendCommand extends Command
15{
16    protected static $defaultName = 'send';
17
18    /**
19     * Configure method
20     *
21     * @return void
22     */
23    public function configure(): void
24    {
25        $this
26            ->setDescription('Send a message to Mattermost')
27            ->setHelp('This command allows you to send a message to Mattermost');
28
29        $this
30            ->addArgument('message', InputArgument::REQUIRED, 'The message to send')
31            ->addOption('channel', null, InputOption::VALUE_OPTIONAL, 'The channel to send the message to')
32            ->addOption('username', null, InputOption::VALUE_OPTIONAL, 'The username to send the message as')
33            ->addOption('icon', null, InputOption::VALUE_OPTIONAL, 'The icon to send the message with')
34            ->addOption('url', null, InputOption::VALUE_OPTIONAL, 'The URL to send the message with');
35    }
36
37    /**
38     * Execute method
39     *
40     * @param \Symfony\Component\Console\Input\InputInterface $input Input interface
41     * @param \Symfony\Component\Console\Output\OutputInterface $output Output interface
42     * @return int
43     */
44    public function execute(InputInterface $input, OutputInterface $output): int
45    {
46        $message = $input->getArgument('message');
47        $channel = $input->getOption('channel');
48        $username = $input->getOption('username');
49        $icon = $input->getOption('icon');
50        $url = $input->getOption('url');
51
52        $client = HttpClient::create();
53
54        $response = $client->request('POST', $url, [
55            'body' => json_encode([
56                'channel' => $channel,
57                'text' => $message,
58                'username' => $username,
59                'icon_url' => $icon,
60            ]),
61            'headers' => [
62                'Content-Type' => 'application/json',
63            ],
64        ]);
65
66        $output->writeln($response->getContent());
67
68        return Command::SUCCESS;
69    }
70}

Let me show you what are parts of this file doing

1public 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).

1public 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.

 1// SendCommand::execute()
 2//...
 3$client = HttpClient::create();
 4$response = $client->request('POST', $url, [
 5    'body' => json_encode([
 6        'channel' => $channel,
 7        'text' => $message,
 8        'username' => $username,
 9        'icon_url' => $icon,
10    ]),
11    'headers' => [
12        'Content-Type' => 'application/json',
13    ],
14]);
15// ...

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

 1<?php
 2
 3require __DIR__ . '/vendor/autoload.php';
 4
 5use Maymeow\ActionMattermostNotify\SendCommand;
 6use Symfony\Component\Console\Application;
 7
 8$c = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
 9
10$app = new Application('Action Mattermost Notify', $c['version']);
11
12$app->add(new SendCommand());
13
14$app->run();

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

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

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

1php 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

1php 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

1`./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.

1wget https://github.com/clue/phar-composer/releases/download/v1.4.0/phar-composer-1.4.0.phar \
2&& chmod +x phar-composer-1.4.0.phar \
3&& 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.

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

After this you can call it like follows

1./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

 1action-mattermost-notify
 2β”œβ”€β”€ action.yml # Added this one
 3β”œβ”€β”€ app.php
 4β”œβ”€β”€ composer.json
 5β”œβ”€β”€ composer.lock
 6β”œβ”€β”€ Dockerfile # Added this one
 7β”œβ”€β”€ entrypoint.sh # Added this one
 8β”œβ”€β”€ src
 9β”‚Β Β  └── SendCommand.php
10└── 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:

 1# Dockerfile
 2# Folloing image has composer-phar preinstaled in it
 3FROM ghcr.io/maymeow/php-ci-cd/php-ci-cd:8.1.6-cs-git-psalm AS build-env
 4
 5WORKDIR /app
 6
 7COPY . /app
 8
 9RUN composer install --no-ansi --no-dev --no-interaction --no-plugins --no-progress --optimize-autoloader --no-scripts
10
11RUN phar-composer build && chmod +x action-mattermost-notify.phar
12
13# Use smallest php image
14FROM php:8.1.6-cli-alpine
15
16COPY --from=build-env /app/action-mattermost-notify.phar /usr/local/bin/action-mattermost-notify
17
18COPY --from=build-env /app/entrypoint.sh /entrypoint.sh
19
20RUN action-mattermost-notify list
21
22ENTRYPOINT ["/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

1#entrypoint.sh
2#!/bin/sh
3
4action-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:

 1# action.yml
 2name: 'Action Name' #change this to your action name
 3author: 'Action Author' # Change this to your
 4description: 'Action Description' # change this
 5branding:
 6  icon: 'command'
 7  color: 'purple'
 8inputs: # following are all inputs that can be used in this action
 9  message:
10    description: 'Enter the message to send to Mattermost'
11    required: true
12  url:
13    description: 'The URL to send the message to'
14    required: true
15  channel:
16    description: 'Enter the channel to send the message to'
17    required: false
18  icon:
19    description: 'Enter the icon to use for the message'
20    required: false
21  username:
22    description: 'Enter the username to use for the message'
23    required: false
24runs: # How this action start? 
25  using: 'docker'
26  image: 'Dockerfile'
27  args:
28    - ${{ inputs.message }}
29    - ${{ inputs.url }}
30    - ${{ inputs.channel }}
31    - ${{ inputs.icon }}
32    - ${{ inputs.username }}

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

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.

If you have published it you can call it as follows

1- name: Action Mattermost Notify
2uses: MayMeow/action-mattermost-notify@v1 # Change this to your action
3with:
4  url: ${{ secrets.MATTERMOST_WEBHOOK }}
5  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

#PHP #Github Actions