Platform.sh is a PaaS platform with support for deployments using git push. The platform currently provides support for PHP, NodeJS, Python and RoR applications (beta), along with associated services like search (Solr, Elasticsearch), databases (Postgres, MySQL, MongoDB), queuing (RabbitMQ) and caching (Redis). The platform is an excellent choice for setting up a development, staging and production environment very quickly and having a robust workflow set up for continuous integration and delivery.
At Axelerant, we are big fans of automation, and in this article, we will see how we can use Platform.sh along with GitLab, Pronto and Behat to set up a workflow wherein the pushed code will go through static code analysis, followed by regression testing.
Our project for this article is going to be a PHP Slim application called HelloUser. The objective of this project is to build a small web app which responds to the user with ‘Hello’ along with their name.
Let’s start off by setting up GitLab.
Create a new project called HelloUser in GitLab.
As soon as we click on the ‘Create project’ button, we’ll have our new project ready to be used.
Copy the Git URL and start your favorite command line tool (I used Mac OS X and terminal for this purpose).
Create a directory called hellouser and move into it. This is where we’ll be storing our code base.
To be able to sync everything we do locally on our workstation with our GitLab server, we’ll need to set up Git. Follow these steps to complete the setup:
Initialize a Git repository - git init
Add a remote called gitlab - git remote add gitlab
git@code.projspace.com:mayank/hellouser.git
Now let’s go ahead and set up Platform.sh.
Log in to Platform.sh by visiting https://accounts.platform.sh and choose to create a new platform.
The following screenshots describe the actions you’ll need to perform in order to get a new project set up on Platform.sh:
Select the ‘Add A Platform’ link.
Choose the appropriate subscription plan. We are going to use the Development plan offered by Platform.sh. More details about subscription plans and pricing can be found at https://platform.sh/pricing.
Choose the region where you want your infrastructure to be provisioned. This can have an impact on latency, so choose the region closest to your users.
Specify the name of the project. In our case we are using ‘HelloUser’ as the name of the project.
Since we have a code repository already set up for the project, we are going to choose the ‘Import your existing code’ option.
Click on the ‘Continue Later’ link and go to the Account Setting section to generate an API token:
Also set up your SSH Keys right now to allow access to Platform.sh servers using SSH.
We’ll need this API token and SSH key pair later on while configuring the setup to do a push to Platform.sh.
Before we start off coding our application, we’ll need to add a couple of environment variables to our project in GitLab.
1. DOCKER_AUTH_CONFIG – This variable contains the Docker authorization configuration to allow the CI jobs to pull Docker images from private repositories. Here is a sample value which can be provided against this key:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "XXXX"
}
}
}
Look into ~/.docker/config.json on your machine to know what values to use for this key.
2. PLATFORM_PROJECT_ID – This variable contains the project id of the project we had setup on Platform.sh.
Let’s get started with coding our application.
Fire up your favorite editor and create a file called composer.json in the hellouser directory which we had created earlier.
{
"require": {
"slim/slim": "3.*"
}
}
Create a .gitignore file
vendor/*
Finally let’s write our application code in a file called index.php
<?php
require __DIR__ . "/vendor/autoload.php";
$app = new Slim\App;
$app->get('/', function($req, $resp) {
$resp->getBody()->write(“Welcome to HelloUser app. Try /YOUR-NAME!”);
return $resp;
});
$app->get(‘/{name}, function($req, $resp) {
$name = $req->getAttribute(‘name’);
$resp->getBody()->write(“Hello, $name”);
return $resp;
}
$app->run();
With our code in place, let’s focus on writing our behat test case. For more information about test driven development you might want to check articles with the QA tag at www.axelerant.com/resources/team-blog.
Create a folder called behat and create the following files:
1. behat/behat.yml
#behat.yml
default:
autoload:
'': %paths.base%/features/bootstrap
suites:
default:
contexts:
- FeatureContext
paths:
- %paths.base%/features
extensions:
Behat\MinkExtension:
browser_name: 'phantomjs'
goutte: ~
javascript_session: selenium2
selenium2:
wd_host: http://127.0.0.1:4444/wd/hub
base_url: ENV_BASE_URL
2. behat/features/hellouser.feature
Feature: Sample test
Background:
Given I am on the homepage
Scenario: Test home page
Then I should see "Welcome"
Scenario: Test a user page
When I am on "/avni"
Then I should see "Hello, foo"
3. behat/features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
}
}
4. Edit composer.json and add the following code segment to it:
"require-dev" : {
"behat/behat" : "3.0.*",
"behat/mink-goutte-driver" : "*",
"behat/mink-browserkit-driver" : "*",
"behat/mink-extension" : "2.*",
"behat/mink-selenium2-driver" : "*",
"behat/mink" : "*"
},
"config": {
"bin-dir": "bin/"
}
5. execute-behat.sh
#!/bin/bash
export ENV_BASE_URL=`cat scripts/platform.url`
echo $ENV_BASE_URL
echo $CI_PROJECT_NAME
echo "---Setup behat---"
composer install
bin/behat --init
cp -r behat/features/* features; cp behat/behat.yml behat.yml
sed -i 's@ENV_BASE_URL@'"$ENV_BASE_URL"'@' behat.yml
sed -i 's@ENV_PROJECT@'"$CI_PROJECT_NAME"'@' behat.yml
echo "---Starting behat---"
bin/behat features/${CI_PROJECT_NAME}.feature
Now it’s time to integrate everything.
Create a file called scripts/push-platform.sh
#!/usr/bin/env bash
set -e
# This script can be configured by specifying different environment variables in
# your .gitlab-ci.yml file's invocation of the script. If those are omitted, as
# in this example, the defaults below and throughout the script should be used.
# Check basic requirements from Config.
if [ -z "$PLATFORM_PROJECT_ID" ]; then
echo "PLATFORM_PROJECT_ID is required, please contact support if you don't know how to do it."
exit 1
fi
# By default we use master as the Platform parent env.
PF_PARENT_ENV=${PF_PARENT_ENV:-master}
# By default we don't allow master to be deployed.
ALLOW_MASTER=${ALLOW_MASTER:-0}
# Prepare the variables.
PF_BRANCH=${PF_DEST_BRANCH:-$CI_BUILD_REF_NAME}
# Platform command path.
CLI_CMD=${CLI_CMD:-"$HOME/.platformsh/bin/platform --yes"}
if [ -z "$PF_BRANCH" ]; then
echo "Source branch (CI_BUILD_REF_NAME or PF_DEST_BRANCH) not defined."
exit 1
fi
# This script is not for production deployments.
if [ "$PF_BRANCH" = "master" ] && [ "$ALLOW_MASTER" != 1 ]; then
echo "Not pushing master branch."
exit
fi
# Set the project for further CLI commands.
COMMAND_SET_REMOTE="${CLI_CMD} project:set-remote ${PLATFORM_PROJECT_ID}"
eval $COMMAND_SET_REMOTE
# Push to PS.
COMMAND_PUSH="${CLI_CMD} push --verbose --force --target=${PF_BRANCH}"
if [ "$PF_PARENT_ENV" != "$PF_BRANCH" ]; then
COMMAND_PUSH="$COMMAND_PUSH --activate --parent=${PF_PARENT_ENV}"
fi
eval $COMMAND_PUSH
# Clean up already merged and inactive environments.
COMMAND_CLEANUP="${CLI_CMD} environment:delete --verbose --inactive --merged --environment=${PF_PARENT_ENV} --exclude=master --exclude="${PF_BRANCH}" --yes --delete-branch --no-wait || true"
eval $COMMAND_CLEANUP
# Store url of new environment in a file
${CLI_CMD} environment:url -e $CI_BUILD_REF_NAME --pipe | head -1 > scripts/platform.url
Let’s create a .gitlab-ci.yml file.
stages:
- push-to-platform
- execute-behat
variables:
DOCKER_DRIVER: overlay
services:
- docker:dind
deploy-hellouser:
image: maxc0d3r/platformshcli:maxc0d3r
variables:
PLATFORMSH_CLI_UPDATES_CHECK: '0'
PF_PARENT_ENV: "master"
ALLOW_MASTER: '1'
stage: push-to-platform
script:
- chmod +x ./scripts/push-to-platform.sh
- script -q -e -c "./scripts/push-to-platform.sh"
artifacts:
paths:
- scripts/platform.url
expire_in: 1 week
tags:
- autoscaler
test-hellouser:
image: maxc0d3r/behat
stage: execute-behat
script:
- echo "---Starting behat---"
- ls -lR
- chmod +x ./behat/execute-behat.sh
- script -q -e -c "./behat/execute-behat.sh"
tags:
- autoscaler
We are making use of two Docker images for our jobs:
maxc0d3r/platformshcli
This is a private Docker image which can be usedto push code to Platform.sh. The code used to build this Docker image is available at https://github.com/axelerant/docker-platformshcli. Fork the repository, modify the code as described in the README and create your own Docker image for use. You’ll need an API token and the SSH Key pair which we’d generated earlier while setting up the project on Platform.sh. We are also using the autoscaling feature of GitLab, and the runners are tagged as autoscaler. We’ll cover more on how to set up autoscaling of GitLab runners in another post.
maxc0d3r/behat
This is a publicly available Docker image with dependencies required for the installation of behat. The code used to build this Docker image is available at https://github.com/axelerant/docker-behat.
We are generating an artifact in the deploy-hellouser job called scripts/platform.url. This file will contain the URL of the application when it’s deployed to Platform.sh. We will be using this file to know the URL in test-hellouser job and configure behat to test against this URL.
Let’s push our code, and voila, we can see the pipelines in action.
So our pipeline failed in the second stage, that is test-hellouser. Let’s look at our job details to see the reason for failure:
Great! So our test failed because it was meant to fail. Let’s push a change into our feature file (behat/features/hellouser.feature):
Feature: Sample test
Background:
Given I am on the homepage
Scenario: Test home page
Then I should see "Welcome"
Scenario: Test a user page
When I am on "/avni"
Then I should see "Hello, avni"
Let’s look back at our pipelines:
Cool! So we have our test cases passing now. We’ll be adding more features into our setup, like static code analysis, in the future. Keep visiting our team blog to know what’s cooking.