The Building of Jetrecord: Episode 3: Git, Capistrano, and A Test Release

January 12, 2009 | 1 Comment |

What Mayer thinks will be essential for continued innovation is for Google to keep its sense of fearlessness. “I like to launch [products] early and often. That has become my mantra,” she says. She mentions Apple Computer and Madonna. “Nobody remembers the Sex Book or the Newton. Consumers remember your average over time. That philosophy frees you from fear.” — Managing Google’s Idea Factory

In the last episode I briefly sketched out what I want to build. In this episode I’m going to start the project by creating a Git repository and deploying a test release with Capistrano.

Cue film.

Download (right-click and save) QuickTime MOV (74 Mb) | QuickTime iPod MP4 (9 Mb) | QuickTime SMIL with captions (74 Mb) [Note: you must choose to open the SMIL file with QuickTime]

A Few Quick Changes

First, I changed my mind about setting up the complicated user management system from the beginning. I realized that I would be violating one of the tenets of Agile/XP: YAGNI. It reminds us to keep focused on the current feature and save details of any related features for the stories that describe them. Flights don’t need to know about the implementation details of users. Therefore, I’m only going to worry about flights for the time being. When called for I will stub out a User model, authentication, et cetera.

Second, I forgot to add a couple gems that will help us write stories and integration tests: Webrat and FactoryGirl. Webrat is a library for writing tests that crawl your site in memory, visiting links, testing inputs, submitting forms, and so on. FactoryGirl is a library that replaces test fixtures with factories. You can install both with RubyGems. We’re not going to need them until the next episode; I just wanted to mention them now.

Some Conventions

Starting the Rails App

Depending on your personality this is either the best part of the project or the worst: the blank canvas. You installed Rails and all of the prerequisites I listed in episode 2. All of our hopes and dreams for a perfect app begin with this command:

$> rails jetrecord

I have set up my development environment to use Apache + Passenger to serve the site and have configured a virtualhost on my local machine to use “ld.com” for the site. (It doesn’t matter if ld.com actually exists or not. As far as my machine is concerned, it points to my local Rails app.) If you’re using Mongrel, Webrick, Thin, or something else as your development server you can run script/server from the RAILS_ROOT.

Since I’m using Apache and I’ve set it to launch at boot time I don’t need to use script/server. I can just browse to ld.com. When I do, I see this.

Rails app home screen for Jetrecord

Great! We’re off and running.

Setting Up Our Repository with Git

Git is installed on my machine. To make a repository, I cd to RAILS_ROOT and do this:

$> git init
Initialized empty Git repository in /Users/harry/Sites/jetrecord/.git/
 
$> git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#	README
#	Rakefile
#	app/
#	config/
#	doc/
#	log/
#	public/
#	script/
#	test/
nothing added to commit but untracked files present (use "git add" to track)

If I run git add right now, Git will recursively add everything in RAILS_ROOT to the repository. But I don’t want that. There are a few items I don’t want to track. To have Git automatically ignore those items, I will create a .gitignore file. Each line tells Git what to ignore.

$> nano -w .gitignore                                                                 
 
config/database.yml
*~
*.cache
*.log
*.pid
tmp/**/*
.DS\_Store
db/cstore/**
doc/*.dot
doc/api
doc/app
doc/plugins
coverage/*
db/*.sqlite3
*.tmproj
Capfile

I save that file. Now I add everything to the repository and make the initial commit.

$> git add .
$> git commit -m "First commit"
 
Created initial commit 4d55351: First commit
 46 files changed, 8525 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 README
 create mode 100644 Rakefile
 create mode 100644 app/controllers/application.rb
 create mode 100644 app/helpers/application_helper.rb
 create mode 100644 config/boot.rb
 create mode 100644 config/environment.rb
 create mode 100644 config/environments/development.rb
 create mode 100644 config/environments/production.rb
 create mode 100644 config/environments/test.rb
 create mode 100644 config/initializers/inflections.rb
 create mode 100644 config/initializers/mime_types.rb
 create mode 100644 config/initializers/new_rails_defaults.rb
 create mode 100644 config/locales/en.yml
 create mode 100644 config/routes.rb
 create mode 100644 doc/README_FOR_APP
 create mode 100644 public/404.html
 create mode 100644 public/422.html
 create mode 100644 public/500.html
 create mode 100755 public/dispatch.cgi
 create mode 100755 public/dispatch.fcgi
 create mode 100755 public/dispatch.rb
 create mode 100644 public/favicon.ico
 create mode 100644 public/images/rails.png
 create mode 100644 public/index.html
 create mode 100644 public/javascripts/application.js
 create mode 100644 public/javascripts/controls.js
 create mode 100644 public/javascripts/dragdrop.js
 create mode 100644 public/javascripts/effects.js
 create mode 100644 public/javascripts/prototype.js
 create mode 100644 public/robots.txt
 create mode 100755 script/about
 create mode 100755 script/console
 create mode 100755 script/dbconsole
 create mode 100755 script/destroy
 create mode 100755 script/generate
 create mode 100755 script/performance/benchmarker
 create mode 100755 script/performance/profiler
 create mode 100755 script/performance/request
 create mode 100755 script/plugin
 create mode 100755 script/process/inspector
 create mode 100755 script/process/reaper
 create mode 100755 script/process/spawner
 create mode 100755 script/runner
 create mode 100755 script/server
 create mode 100644 test/performance/browsing_test.rb
 create mode 100644 test/test_helper.rb

The application is now being tracked by Git. One last thing before we move on to Capistrano. I’m going to change the default index page to show a simple “Hello, World.” I’m using TextMate, which is the reason for the mate command.

$> mate public/index.html

All I did was replace the dynamic stuff on the home page with “Hi.” You’ll see it in a minute. Alright. That’s all I’m going to do for the first release. Now I need to move the repository to a remote location so I can pull from it when I release new code.

$> ssh REMOTE
REMOTE $> mkdir etc/git/jetrecord.git
REMOTE $> cd etc/git/jetrecord.git
REMOTE $> git init --bare
Initialized empty Git repository in /home/user/etc/git/jetrecord.git/
REMOTE $> exit
Connection closed
 
$> git remote add origin ssh://user@REMOTE/home/user/etc/git/jetrecord.git
$> git push origin master
Counting objects: 68, done.
Compressing objects: 100% (61/61), done.
Writing objects: 100% (68/68), 80.67 KiB, done.
Total 68 (delta 17), reused 0 (delta 0)
To ssh://user@REMOTE/home/user/etc/git/jetrecord.git
 * [new branch]      master -> master

Good. Now I have a remote Git repository. Any changes that I push will go to this remote location that will also be accessible to Capistrano. And so we move on to Capistrano.

Capistrano

I have the Capistrano gem installed. I just need to initialize it for my app and then fix up my deployment recipe.

$> capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!
 
$> mate config/deploy.rb

Here’s my deploy.rb

set :use_sudo, false
set :application, "jetrecord"
set :repository,  "ssh://user@REMOTE/home/user/etc/git/jetrecord.git"
set :deploy_to, "/var/www/apps/#{application}"
set :user, 'user'
set :runner, user
set :scm, :git
set :domain, 'REMOTE'
role :app, domain
role :web, domain
role :db,  domain, :primary => true
set :server_name, "landingdeparting.com"
set :server_alias, "*.landingdeparting.com"
depend :remote, :command, :gem
 
# Allow ssh to use ssh keys
set :ssh_options, { :forward_agent => true }
 
deploy.task :restart do
  # Restart Passenger
  run "touch #{current_path}/tmp/restart.txt"
end
 
deploy.task :symlinks do
  run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
 
after :deploy, 'deploy:cleanup'
after 'deploy:update_code', 'deploy:symlinks'

Cool. Commit the changes.

$> git add .
$> git commit -a -m "First stab at Capistrano deployment recipe"
Created commit e80f9dc: First stab at Capistrano deployment recipe
 1 files changed, 28 insertions(+), 0 deletions(-)
 create mode 100644 config/deploy.rb
 
$> git push
Counting objects: 6, done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 775 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
To ssh://user@REMOTE/home/user/etc/git/jetrecord.git
   ba68814..e80f9dc  master -> master

Okay, one more thing on the remote end. In my deploy recipe I added a symlink from the database.yml file in the shared directory to the RAILS_ROOT/config directory. I’m doing this because I don’t want to track database.yml in my Git repository and it’s cheaper to create a symlink than to copy an actual file from one release to the next. But I still need to create the file manually. I’ll do that now.

$> ssh REMOTE
REMOTE $> mkdir /var/www/apps/jetrecord
REMOTE $> mkdir /var/www/apps/jetrecord/shared
REMOTE $> mkdir /var/www/apps/jetrecord/shared/config
REMOTE $> nano -w /var/apps/jetrecord/shared/config/database.yml
 
production:
  adapter: postgresql
  database: jetrecord_production
  user: dbuser
  password: pass
  pool: 5
  timeout: 5000

Save and exit. Don’t forget to set up the actual database on the remote server with the correct credentials!

With that done, all that’s left to do is make sure my domain name is set up and ready to be seen by the world. This will depend on a great number of things, such as where your site is hosted, what operating system it’s on, what DNS services you’re using, and so on.

I’m not a sysadmin and I’m not going to pretend to be one. The focus of this series is the application so I’ll move on assuming that we’ve got our domain set up and we’ve tested that we can reach the site with a text index page of some kind.

Now we’ll run the setup command, check our deployment recipe, and then do a deploy. For the deploy I’m going to run cap:deploy instead of cap:deploy:cold because I don’t have any database migrations to run yet.

$> cap deploy:setup
  * executing 'deploy:setup'
  * executing "mkdir -p /var/www/apps/jetrecord /var/www/apps/jetrecord/releases /var/www/apps/jetrecord/shared /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/shared/pids &&  chmod g+w /var/www/apps/jetrecord /var/www/apps/jetrecord/releases /var/www/apps/jetrecord/shared /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/shared/pids"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 
$> cap deploy:check
  * executing 'deploy:check'
  * executing "test -d /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "test -w /var/www/apps/jetrecord"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "test -w /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "which git"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "which gem"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
You appear to have all necessary dependencies installed
 
$> cap deploy
  * executing 'deploy'
  * executing 'deploy:update'
 ** transaction: start
  * executing 'deploy:update_code'
    executing locally: "git ls-remote ssh://user@REMOTE.joyent.us/home/user/etc/git/jetrecord.git HEAD"
  * executing "git clone -q ssh://user@REMOTE/home/user/etc/git/jetrecord.git /var/www/apps/jetrecord/releases/20090108224035 && cd /var/www/apps/jetrecord/releases/20090108224035 && git checkout -q -b deploy dbd8430254cd5bd2860c2b73349c149b1038512f && (echo dbd8430254cd5bd2860c2b73349c149b1038512f > /var/www/apps/jetrecord/releases/20090108224035/REVISION)"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing 'deploy:finalize_update'
  * executing "chmod -R g+w /var/www/apps/jetrecord/releases/20090108224035"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "rm -rf /var/www/apps/jetrecord/releases/20090108224035/log /var/www/apps/jetrecord/releases/20090108224035/public/system /var/www/apps/jetrecord/releases/20090108224035/tmp/pids &&\\\n      mkdir -p /var/www/apps/jetrecord/releases/20090108224035/public &&\\\n      mkdir -p /var/www/apps/jetrecord/releases/20090108224035/tmp &&\\\n      ln -s /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/releases/20090108224035/log &&\\\n      ln -s /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/releases/20090108224035/public/system &&\\\n      ln -s /var/www/apps/jetrecord/shared/pids /var/www/apps/jetrecord/releases/20090108224035/tmp/pids"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "find /var/www/apps/jetrecord/releases/20090108224035/public/images /var/www/apps/jetrecord/releases/20090108224035/public/stylesheets /var/www/apps/jetrecord/releases/20090108224035/public/javascripts -exec touch -t 200901082240.37 {} ';'; true"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
    triggering after callbacks for 'deploy:update_code'
  * executing 'deploy:symlinks'
  * executing "ln -nfs /var/www/apps/jetrecord/shared/config/database.yml /var/www/apps/jetrecord/releases/20090108224035/config/database.yml"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing 'deploy:symlink'
  * executing "rm -f /var/www/apps/jetrecord/current && ln -s /var/www/apps/jetrecord/releases/20090108224035 /var/www/apps/jetrecord/current"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 ** transaction: commit
  * executing 'deploy:restart'
  * executing "touch /var/www/apps/jetrecord/current/tmp/restart.txt"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
    triggering after callbacks for 'deploy'
  * executing 'deploy:cleanup'
  * executing "ls -xt /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 ** keeping 5 of 6 deployed releases
  * executing "rm -rf /var/www/apps/jetrecord/releases/20090108222452"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished

Okay, done. Now we check to see if landingdeparting.com shows my Rails app.

LD says 'hi'

So that’s it. We have our Rails project started. We have our source code in a Git repository. We can deploy the app to our server with Capistrano. And we can view our handiwork live on the web. Now it’s time to build the real application. We’ll take it bird by bird.

Epilogue

From now on when I type something into the terminal I’m only going to show the output of the command if I think it’s relevant for the topic of the episode. In this episode, for instance, I set up Git and Capistrano and I thought it would be good to see how these programs respond. In future episodes I’m not going to show output from Git or Capistrano unless it’s relevant.

I’m going to follow the same general rule for other commands. If it’s relevant to the topic at hand or if a special case needs to be addressed, I’ll show it. Otherwise, I’ll only show the commands I type in. You can also assume that I am making regular commits to git and pushing those changes live with Capistrano. I don’t intend to show you every commit message and the result of every deployment here in the text unless there’s a special case. Some of these things may show up in the videos, but they get a little tedious here. Does that make sense? Each episode will build on previous episodes with regards to the minutiae that I share.

Coming Up in the Next Episode

I’m going to start building the first feature by writing some tests and then writing the code that implements the feature. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Git: the fast version control system

Capistrano

Phusion Passenger

Pragmatic Version Control Using Git by Travis Swicegood

Deploying Rails Applications by Ezra Zygmuntowicz, Bruce Tate, and Clinton Begin

Creative Commons License The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

| Tags: Portfolio, Programming, Ruby on Rails, Video

1 Comment

  1. Episode 3 of The Building of Jetrecord : The Online Logbook for Pilots : Jetrecord spake saying:

    [...] third installment in the series is available now. We start our application, commit it to a repository, and then run a [...]