Getting Started with a Real World Application on platform.sh

This is the second in a series of articles involving the writing and launching of my DurableDrupal Lean ebook series website on platform.sh. Since it's a real world application, this article is for real world website and web application developers. If you are starting from scratch but enthusiastic and willing to learn, that means you too. I'm fortunate enough to have their sponsorship and full technical support, so everything in the article has been tested out on the platform. A link will be edited in here as soon as it goes live.

Diving in

Diving right in I setup a Trello Kanban Board for Project Inception as follows:

Project Inception Kanban

Both Vision (Process, Product) and Candidate Architecture (Process, Product) jobs have been completed, and have been moved to the MVP 1 column. We know what we want to do, and we're doing it with Drupal 7, based on some initial configuration as a starting point (expressed both as an install profile and a drush configuration script). At this point there are three jobs in the To Do column, constituting the remaining preparation for the Team Product Kickoff. And two of them (setup for continuous integration and continuous delivery) are about to be made much easier by virtue of using platform.sh, not only as a home for the production instance, but as a central point of organization for the entire development and deployment process.

Beginning Continuous Integration Workflow

What we'll be doing in this article:

Overcoming the confusion between Continuous Integration (team development with a codebase) and Continuous Delivery (deploying to an environment).

"So what is CI? In short, it is an integration of code into a known or working code base.... The top benefits are to provide fast feed back to the members of the team and to ensure any new changes don’t break the working branch."

"CD... is an automated process to deliver a software package to an environment.... we can now extend the fast feedback loops and reduction of constraints with packaging techniques, automation workflows, and integrated tools that keep track of the software versions in different environments."

"CI and CD are two completely separated practices that are tightly interlocked to create a unified ALM [Application Lifecycle Management] workflow." – Bryan Root

And let's take it step by step by breaking the CI Workflow Job into tasks:

Continuous Integration Tasks

Create Ansible playbook to create local development VM suitable for working with platform.sh

Based on Jeff Geerling's great work both on Ansible itself as well as its use with Drupal provisioning, I whipped up and tested ansible-vm-platformsh. Dependencies:

Clone the playbook from GitHub into a workspace on local dev box and vagrant up

With the dependencies installed, and once the ubuntu/trusty box was downloaded (I had already used it before on several projects), it only took a few minutes to bring up our local dev box:

$ git clone git@github.com:DurableDrupal/ansible-vm-platformsh.git platformsh-vk-sandbox
$ cd platformsh-vk-sandbox
$ vagrant up

The box is brought up in a private network with ip 192.168.19.46 (editable in Vagrantfile). So to be able to bring up the website in a local browser by name, I edited my dev box's /etc/hosts file by including the following line in accordance with the Virtual Host template parameters (editable in provisioning/vars.yml):

$ grep platformsh /etc/hosts
192.168.19.46 platformshvk.dev

ssh into the local dev box, log into platform.sh and manage ssh keys

platformsh-vk is a newly provisioned development box, so the user vagrant hasn't yet got a set of public and private keys for the purpose of securely authenticating onto the platform.sh account using ssh.

We first login to our local newly provisioned dev box (that's already configured by playbook operations), also using ssh:

$ vagrant ssh
vagrant@vagrant-ubuntu-trusty-64:~$
vagrant@vagrant-ubuntu-trusty-64:~$ ssh-keygen -t rsa -C "me@mymail.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/vagrant/.ssh/id_rsa.
Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub.

The Ansible Playbook has already installed the platform.sh CLI (command-line interface), called, appropriately enough, platform, so we're all set!

vagrant@vagrant-ubuntu-trusty-64:~$ cd /var/www
vagrant@vagrant-ubuntu-trusty-64:/var/www$ ls -l
total 4
drwxr-xr-x 2 root root 4096 Mar 22 16:10 html

The first time you execute the CLI you will be asked to login with your platform.sh account.

vagrant@vagrant-ubuntu-trusty-64:/var/www$ platform
Welcome to Platform.sh!
Please log in using your Platform.sh account
Your email address: me@mymail.com
Your password:
Thank you, you are all set.
Your projects are:
+---------------+---------------------+-------------------------------------------------+
| ID            | Name                | URL                                             |
+---------------+---------------------+-------------------------------------------------+
| myproject | Victor Kane Sandbox | https://us.platform.sh/#/projects/myproject |
+---------------+---------------------+-------------------------------------------------+
Get a project by running platform get [id].
List a project's environments by running platform environments.
Manage your SSH keys by running platform ssh-keys.
Type platform list to see all available command

Now, you can manage your keys from your online sandbox on platform.sh. But by using the platform CLI you can do anything from your local dev box that can be done online. To upload your public key, we did:

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev$ platform ssh-keys
Add a new SSH key by running platform ssh-key:add [path]
Delete an SSH key by running platform ssh-key:delete [id]
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev$ platform ssh-key:add ~/.ssh/id_rsa.pub
Enter a name for the key: vkvm

In a single line:

$ platform ssh-key:add --name=vkvm0323 ~/.ssh/id_rsa.pub
The SSH key id_rsa.pub has been successfully added to your Platform.sh account

Get the project and sync the platform server and local databases

The Ansible playbook has already set up the virtual host to be edited in the file /etc/apache2/sites-available/platformshvk.dev.conf and will expect the project to be cloned under /var/www. So to get the project from the platform server, we first locate ourselves at /var/www/platformsh-vk-dev and then get the project. Permissions have already been taken care of in this directory anticipating that we are operating locally as the user vagrant in the dev box.

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev$ platform get myproject
  Cloning into 'myproject/repository'...
  Warning: Permanently added the RSA host key for IP address '54.210.49.244' to the list of known hosts.
Downloaded myproject to myproject
Building application php using the toolstack php:drupal
  Beginning to build                                                   [ok]
  /var/www/platformsh-vk-dev/myproject/repository/project.make.
  drupal-7.34 downloaded.                                              [ok]
  drupal patched with                                                  [ok]
  install-redirect-on-empty-database-728702-36.patch.
  Generated PATCHES.txt file for drupal                                [ok]
  platform-7.x-1.3 downloaded.                                         [ok]
Saving build archive...
Creating file: /var/www/platformsh-vk-dev/myproject/shared/settings.local.php
Edit this file to add your database credentials and other Drupal configuration.
Creating directory: /var/www/platformsh-vk-dev/myproject/shared/files
This is where Drupal can store public files.
Symlinking files from the 'shared' directory to sites/default
Build complete for application php

In order to sync the local database (already created with the Ansible playbook, you guessed it!) from the platform server, we must first enter the credentials (user root and name taken from the domain variable on line 105 of provisioning/playbook.yml) into the settings.local.php file created in the shared sub-directory of the project build.

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/shared$ cat settings.local.php
<?php

// Database configuration.
$databases['default']['default'] = array(
  'driver' => 'mysql',
  'host' => 'localhost',
  'username' => 'root',
  'password' => '',
  'database' => 'platformshvk',
  'prefix' => '',
);

Now let's grab the drush aliases and sync databases! First I did a remote drush status on the platform

$ platform drush status

Then I grabbed the aliases:

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject$ platform drush-aliases
Aliases for Victor Kane Sandbox (myproject):
    @myproject._local
    @myproject.master

And used them to sync the local dev box database with what is on the platform server:

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject$ drush sql-sync @myproject.master @myproject._local
WARNING:  Using temporary files to store and transfer sql-dump.  It is recommended that you specify --source-dump and --target-dump options on the command line, or set '%dump' or '%dump-dir' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.
You will destroy data in platformshvk and replace with data from ssh.us.platform.sh/main.
You might want to make a backup first, using the sql-dump command.
Do you really want to continue? (y/n): y
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject$

Configure local dev virtual host and bring up website

We can now confirm that our local instance is alive and well via a drush status:

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject$ cd www
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/www$ drush status
 Drupal version                  :  7.34
 Site URI                        :  http://default
 Database driver                 :  mysql
 Database username               :  root
 Database name                   :  platformshvk
 Database                        :  Connected
 Drupal bootstrap                :  Successful
 Drupal user                     :  Anonymous
 Default theme                   :  bartik
 Administration theme            :  seven
 PHP executable                  :  /usr/bin/php
 PHP configuration               :  /etc/php5/cli/php.ini
 PHP OS                          :  Linux
 Drush version                   :  6.6-dev
 Drush configuration             :
 Drush alias files               :  /home/vagrant/.drush/myproject.aliases.drushrc.php
 Drupal root                     :  /var/www/platformsh-vk-dev/myproject/builds/2015-03-23--11-27-44--master
 Site path                       :  sites/default
 File directory path             :  sites/default/files
 Temporary file directory path   :  /tmp

We now configure the virtual host on the dev box to take into account the project name. After editing:

$ sudo vi /etc/apache2/sites-available/ 
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName platformshvk.dev
    ServerAlias www.platformshvk.dev
    DocumentRoot /var/www/platformsh-vk-dev/myproject/www
    <Directory "/var/www/platformsh-vk-dev/myproject/www">
        Options FollowSymLinks Indexes
        AllowOverride All
    </Directory>
</VirtualHost> 

After reloading the Apache server configuration we can point our browser at the local web server

Local website instance

Exercise CI workflow by doing an upgrade and pushing via repo to platform

First we find out what needs updating

$ drush pm-update
Update information last refreshed: Sun, 03/22/2015 - 19:21
 Name    Installed Version  Proposed version  Message
 Drupal  7.34               7.35              SECURITY UPDATE available

Don't update with drush! Instead edit drush makefile and rebuild locally to test

We edit drush make file with the change in Drupal core version

$ cd repository
$ vi project.make
api = 2
core = 7.x
; Drupal core.
projects[drupal][type] = core
projects[drupal][version] = 7.35

We build locally to test

$ platform project:build
Building application php using the toolstack php:drupal
  Beginning to build                                                   [ok]
  /var/www/platformsh-vk-dev/myproject/repository/project.make.
  drupal-7.35 downloaded.                                              [ok]
  drupal patched with                                                  [ok]
  install-redirect-on-empty-database-728702-36.patch.
  Generated PATCHES.txt file for drupal                                [ok]
  platform-7.x-1.3 downloaded.                                         [ok]
Saving build archive...
Symlinking files from the 'shared' directory to sites/default
Build complete for application php

We confirm that the version has indeed been updated interactively in the browser.

Push to platform (automatically rebuilds everything on master!)

Wow! Talk about “everything in code”:

vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/repository$ git config --global user.email me@mymail.com
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/repository$ git config --global user.name jimsmith
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/repository$ git commit -am "Updated Drupal core to 7-35"
[master 6a0f997] Updated Drupal core to 7-35
 1 file changed, 1 insertion(+), 1 deletion(-)
vagrant@vagrant-ubuntu-trusty-64:/var/www/platformsh-vk-dev/myproject/repository$ git push origin master
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 295 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
Validating submodules.
Validating configuration files.
Processing activity: **Victor Kane** pushed to **Master**
    Found 1 new commit.
    Building application 'php' with toolstack 'php:drupal' (tree: b53af8a)
      Installing build dependencies...
        Installing php build dependencies: drush/drush
      Making project using Drush make...
        Executing `drush -y make --cache-duration-releasexml=300 --concurrency=8 project.make /app/out/public`...
          Beginning to build project.make.                                            [ok]
          drupal-7.35 downloaded.                                                     [ok]
          drupal patched with                                                         [ok]
          install-redirect-on-empty-database-728702-36.patch.
          Generated PATCHES.txt file for drupal                                       [ok]
          platform-7.x-1.3 downloaded.                                                [ok]
      Moving checkout directory to `/sites/default`.
      Detected a `/sites/default` directory, initializing Drupal-specific files.
      Creating a default `/sites/default/settings.php` file.
      Creating the environment-specific `/sites/default/settings.local.php` file.
      Executing pre-flight checks...
      Compressing application.
      Beaming package to its final destination.
    W: Route 'www.{default}' doesn't map to a domain of the project, mangling the route.
    W: Route '{default}' doesn't map to a domain of the project, mangling the route.
    Re-deploying environment myproject-master.
      Environment configuration:
        php: size M
        mysql: size M
        redis: size M
        solr: size M
      Environment routes:
        http://master-myproject.us.platform.sh/ is served by application `php`
        http://www---master-myproject.us.platform.sh/ redirects to http://master-myproject.us.platform.sh/
        https://master-myproject.us.platform.sh/ is served by application `php`
        https://www---master-myproject.us.platform.sh/ redirects to http://master-myproject.us.platform.sh/
To myproject@git.us.platform.sh:myproject.git
   f2e12d8..6a0f997  master -> master

We confirm via browser pointed to platform server that the update has been effectuated.

Next time we'll drill down into some serious development workflow by implementing the startup landing page for the website.