Ryan Kanno: The diary of an Enginerd in Hawaii

Everything you've ever thought, but never had the balls to say.

My LinkedIn Profile
Follow @ryankanno on Twitter
My Feed

2008 Archives

Another turning point, a fork stuck in the road…

“50 years from now, when you look back at your life, don’t you want to say you had the guts to get in the car?”

Shia LaBeouf as Sam Witwicky, Transformers

As some of you may know, I recently left my position as a Navy contractor at COMPACFLT for another position at a small, local technology startup. Having come from a family of “lifers”, it wasn’t an easy decision. Even though I absolutely loved both the work and people, I felt that I would have too many regrets not taking this opportunity.

So with that said, here’s a few of my lasting memories:

  • Late nights in the dreaded server room with Wally and co.
  • The lunchroom gang – Our topics ranged the gamut; from MMA to the next 4chan meme.
  • Admiral’s Cup – We won a basketball game! (Not by forfeit!)
  • Ping pong Fridays!
  • Almost killing my co-worker Cindy with the Wii controller… and then my yo-yo.
  • Monday morning chats with Gits about his weekend PS3 adventures.
  • Steve’s yellow lunch truck

Though I came in a contractor, I feel as though I’m leaving as family. So with that said, farewell, good luck, and Godspeed to everyone at FLT!

Click on my picture to check out my flickr set!

Popularity: 19% [?]

Tagged: , , , , .


Using Capistrano to deploy to WebFaction

Update

If you like to stay on the edge, check out my latest post describing how to use Capistrano 2.5 to deploy Rails 2.3 to Webfaction!


There’s nothing I love more than sweet automation.

After spending the better part of an hour searching the great Googs, there was only a single blog I could find describing how to use Capistrano to deploy to WebFaction. Unfortunately, Justin was describing a Capistrano 1.4 deployment. I found a few posts on the WebFaction forums, but nothing concrete. So after a few hours fiddling with the technology, here’s how I configured my Rails 2.1.1 project to use Capistrano 2.5 to deploy to WebFaction.

Assumptions

Before getting started, I’m going to assume the following:

  • I’m assuming you’ve already used the one-click WebFaction goodness to create a brand new Rails application in ~/webapps/<application_name>. If you don’t know what I’m referring to, make sure to check out the Rails and Typo Demo screencast. Make sure you have a domain, application, and website configured.
  • I’m also going to assume that your nifty Rails application is safely stored away in either a Subversion or Git repository and you’ve frozen Rails in your application.
  • Finally, I’m going to assume you setup your database via WebFaction’s control panel.

Installing Capistrano

The very first thing you have to do is install Capistrano on your local machine by issuing the following command:

$ gem install -y capistrano

After installing Capistrano, the first thing you have to do is to “capify” your local Rails project. Change into your project’s root directory and issue the following command:

$ capify .

This configures your Rails project to play nicely with Capistrano. Two files should’ve been created; Capfile in the project root and config/deploy.rb. The deploy.rb file contains the Rails project application-specific deployment configuration.

Configuring WebFaction

Jumping back to WebFaction, I followed a few of the steps in Justin’s blog. First thing’s first, ssh into your WebFaction account and create a directory called webapps-releases in your home directory. This directory is where we’re going to deploy the application to.

Since you’ve already configured a Rails application at ~/webapps/<application_name>, change into that directory. You should see a standard Rails project with the exception of an extra file called autostart.cgi. Remove everything in the directory except the autostart.cgi file by issuing the following commands:

$ cd ~/webapps/<application_name>
$ mv autostart.cgi ~/
$ rm -rf *
$ mv ~/autostart.cgi .

Once the directory is clear, create a symlink to the log directory that will be in the webapps-releases directory we created earlier.

$ ln -s ~/webapps-releases/<application_name>/shared/log ~/webapps/<application_name>/log

Note: I’m assuming here that the WebFaction app and the Rails application have identical names.

Next, open up your favorite editor of choice (*cough*Vi*cough*) and edit the autostart.cgi file. Jump to the end of the file and comment out the following line:

1
2
 
# os.system('/usr/local/bin/mongrel_rails start -d -e production -P /home/<webfaction_username>/webapps/<webfaction_app_name>/log/mongrel.pid -p <port>')

and right below it, cut and paste the following:

1
2
 
  os.system('/usr/local/bin/mongrel_rails start -c /home/<webfaction_username>/webapps-releases/<webfaction_app_name>/current -d -e production -P /home/<webfaction_username>/webapps/<webfaction_app_name>/log/mongrel.pid -p <port>')

Creating your custom deploy.rb

After configuring WebFaction, we have to configure the Capistrano application deployment configuration. On your local machine, find the file config/deploy.rb and replace it with the one below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 
set :webfaction_username, "<webfaction_username>"
set :webfaction_db_type, "<webfaction_db_type>"
set :webfaction_db, "<webfaction_db>"
set :webfaction_db_username, "<webfaction_db_username>"
set :webfaction_port, "<webfaction_port (get from autostart.cgi)>"
set :database_yml_template, "database.example.yml"
 
set :application, "test"
set :deploy_to, "/home/#{webfaction_username}/webapps-releases/#{application}"
 
set :scm, :subversion
set :scm_user, "<scm_username>"
set :scm_password, Proc.new { Capistrano::CLI.password_prompt("Subversion password for #{scm_user}: ") }
set :repository, Proc.new { "--username #{scm_user} --password #{scm_password} --no-auth-cache <http://path/to/your/svn/goes/here/>"} 
 
set :user, "#{webfaction_username}"
set :use_sudo, false 
 
set :domain, "<webfaction_domain>"
 
role :app, domain
role :web, domain
role :db,  domain, :primary => true
 
desc "Symlink public to what webfaction expects the webroot to be"
task :after_symlink, :roles => :web do
  run "ln -nfs #{release_path}/public /home/#{webfaction_username}/webapps/#{application}/"
end
 
namespace :deploy do
 
  # Taken from http://jonathan.tron.name/2006/07/15/capistrano-password-prompt-tips 
  # Thanks Jonathan! :)
  desc "Creates the database configuration on the fly"
  task :create_database_configuration, :roles => :app do
    require "yaml"
    set :production_db_password, proc { Capistrano::CLI.password_prompt("Remote production database password: ") }
 
    db_config = YAML::load_file("config/#{database_yml_template}")
    db_config.delete('test')
    db_config.delete('development')
 
    db_config['production']['adapter'] = "#{webfaction_db_type}"
    db_config['production']['database'] = "#{webfaction_db}"
    db_config['production']['username'] = "#{webfaction_db_username}"
    db_config['production']['password'] = production_db_password
    db_config['production']['host'] = "localhost"
 
    put YAML::dump(db_config), "#{release_path}/config/database.yml", :mode => 0664
  end
 
  after "deploy:update_code", "deploy:create_database_configuration"
 
  desc "Redefine deploy:start"
  task :start, :roles => :app do
    invoke_command "/usr/local/bin/mongrel_rails start -c #{deploy_to}/current -d -e production -P /home/#{webfaction_username}/webapps/#{application}/log/mongrel.pid -p #{webfaction_port}", :via => run_method
  end
 
  desc "Redefine deploy:restart"
  task :restart, :roles => :app do
    invoke_command "/usr/local/bin/mongrel_rails restart -c #{deploy_to}/current -P /home/#{webfaction_username}/webapps/#{application}/log/mongrel.pid", :via => run_method
  end
 
  desc "Redefine deploy:stop"
  task :stop, :roles => :app do
    invoke_command "/usr/local/bin/mongrel_rails stop -c #{deploy_to}/current -P /home/#{webfaction_username}/webapps/#{application}/log/mongrel.pid", :via => run_method
  end
end
Note: Change all the values in tags like <webfaction_username>, <webfaction_db>, <webfaction_db_username>, etc. to those values that fit your configuration!
Otherwise, this file in itself won’t do you any good.

Props out to Jonathan for the fantastic Capistrano tips!

After copying the deploy.rb file and editing the appropriate variables, run the following command in your Rails project’s root directory:

$ cap deploy:setup

This command creates the appropriate directory structure for Capistrano on the deployment server based upon values set in your deploy.rb. Next, run the following command to check your dependencies.

$ cap deploy:check

If everything is successful, you should see a message that reads something like…

You appear to have all necessary dependencies installed

Next, push your code out to the server using the following command:

$ cap deploy:update

Finally, to start up your application run the following Capistrano command:

$ cap deploy:start

Now, you should be able to run the standard Capistrano tasks to deploy your application to WebFaction!

Explanation

Most techies like to have an explanation of what’s going on with the Capistrano deploy.rb. I could probably write another blog about it, but I’m lazy (and pressed for time). The :create_database_configuration task basically writes the database.yml production configuration on the fly (courtesy of this blog posting).

The basic gyst of the rest of the script is that WebFaction is proxying a Mongrel instance. The Capistrano deploy.rb override the original deploy:start, deploy:stop, and deploy:restart tasks to run Mongrel commands that WebFaction can understand. Typically, the default Capistrano tasks run script/spin and reaper, but it was easier just to redefine the task. If anyone has any tips/suggestions to improve the script, I’m all ears!

Voila! (Enjoy)

Popularity: 28% [?]

Tagged: , , , , , .


web2email.py – A web to email Python backup script

I’m back, at least for the time being. There’s definitely a calm before the impending storm, but until then, I’m back posting little tidbits of uselessness. Enjoy!

Python goodness

While introducing the concept of automation to a friend of mine, I came across a requirement to archive a series of URL’s on a daily basis. Luckily for me, the URL’s consisted primarily of plain text. Loading up VIM, I concocted this Python script in a few hours – most of which was spent searching Googs <3.

If you're looking for a true web crawler, this won't be for you - though loading up lxml/Beautiful Soup, cssutils, and a Javascript parser to determine what artifacts need to be downloaded shouldn’t be all that difficult…

But, I’ll leave that as an exercise for the reader (That’s you, btw!)

In any case, the following script crawls a URL and sends the page via Googs or Webfaction via SMTP-AUTH or via a plain SMTP server of your choosing. Sorta-kinda like having your own WayBackMachine. In any case, cut and paste the following into a neat file called web2email.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#! /usr/bin/env python2.5
# -*- coding: utf-8 -*-
#
# Copyright (c) 2008 Ryan Kanno (ryankanno@localkinegrinds.com)
# License: GNU GPLv3
 
import urllib2
 
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import datetime
 
from optparse import OptionParser
import sys, logging
 
__doc__ = """
 
This script retrieves a URL and sends its contents via email to 
a list of recipients.  Typically, this script is run from a cron
job that sends emails to a Gmail account to archive the contents
of a URL.
 
Mail can be sent via normal or authenticated SMTP.  Tested using 
Gmail SMTP (authenticated), Webfaction SMTP (authenticated), and
localhost (normal).
 
Example:
 
Sends the contents of http://www.espn.com to friend@domain.com using your Gmail settings
 
    python web2email.py -u gmail_username \
                        -p gmail_password \
                        -f gmail_username@gmail.com \
                        -r friend@domain.com http://www.espn.com
 
Sends the contents of http://www.espn.com to friend@domain.com using your Webfaction settings
 
    python web2email.py -u webfaction_username \
                        -p webfaction_password \
                        -f webfaction_account@webfaction_domain.com \
                        -s smtp.webfaction.com \
                        -r friend@domain.com http://www.espn.com
 
Sends the contents of http://www.espn.com to friend@domain.com using your local settings
 
    python web2email.py -f your_email@domain.com \
                        -s localhost \
                        --port 25 \
                        -r friend@domain.com http://www.espn.com
"""
 
__author__  = "ryankanno@localkinegrinds.com"
__url__     = "http://blog.localkinegrinds.com"
__version__ = "0.1"
 
USAGE = "usage: %prog [options] url" 
DESC  = __doc__.split('\n\n')[0]
 
def configure_logging(log_level, format='%(asctime)s %(levelname)s %(message)s'):
    logging.basicConfig(level=log_level, format=format)
 
def _validate_options_and_args(parser, options, args):
    logging.debug("Validating options and arguments.")
    if (len(args) != 1):
        parser.error("Incorrect number of arguments.  Script expects 1 (URL to backup), but received %i." % len(args))
        sys.exit(2) # Command line syntax error
    elif not options.recipients: 
        parser.error("You must include at least one recipient.")
        sys.exit(1) 
    elif (options.username and options.password is None) or (options.username is None and options.password is not None):
        parser.error("You must include both a username and password.")
        sys.exit(1) 
    elif not options.from_email:
        parser.error("You must include a valid from email address.")
        sys.exit(1) 
 
def getPage(url):
    logging.debug("Attempting to retrieve %s" % url)
    try:
        response = urllib2.urlopen(url)
        return response.read()
    except urllib2.HTTPError, e:
        logging.error("HTTPError (%s) occurred retrieving %s" % (e.code, url))
        sys.exit(1)
    except urllib2.URLError, e:
        logging.error("URLError (%s) occurred retrieving %s" % (e.reason, url))
        sys.exit(1)
 
def mail(send_from, send_to, subject, text, content_type, files=[], server='localhost', port=25, username=None, password=None):
 
    def _auth(server, port, username, password):
        logging.debug("Attempting to send email via %s:%i using the following credentials (%s:%s)." % (server, port, username, password))
        smtp = smtplib.SMTP(server, port) 
        smtp.ehlo()
        smtp.starttls()
        smtp.ehlo()
        smtp.login(username, password)
        smtp.sendmail(username, send_to, msg.as_string())
        smtp.close()
 
    def _unauth(server, port):
        logging.debug("Attempting to send email via %s:%i" % (server, port))
        smtp = smtplib.SMTP(server, port)
        smtp.sendmail(send_from, send_to, msg.as_string())
        smtp.close()
 
    assert type(send_to)==list
 
    msg=MIMEMultipart()
    msg['From'] = send_from
    msg['To'] = COMMASPACE.join(send_to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject
 
    text = MIMEText(text)
    text.set_type(content_type)
    text.set_param('charset', 'UTF-8')
 
    msg.attach(text)
 
    for f in files:
        part = MIMEBase('application', "octet-stream")
        part.set_payload(open(file,"rb").read())
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
        msg.attach(part)
 
    if not username and not password:
        _unauth(server, port)
    else:
        _auth(server, port, username, password) 
 
def main():
    parser = OptionParser(usage=USAGE, description=DESC)
 
    parser.add_option("-u", "--username", dest="username", metavar="USER", help="Username to SMTP server")
    parser.add_option("-p", "--password", dest="password", metavar="PWD", help="Password to SMTP server")
    parser.add_option("-s", "--server", dest="server", metavar="SERVER", help="SMTP server (Defaults to Gmail)")
    parser.add_option("--port", dest="port", metavar="PORT", type="int", help="SMTP server port (Defaults to Gmail)")
    parser.add_option("-f", "--from", dest="from_email", metavar="FROM", help="From address")
    parser.add_option("-r", "--recipient", action="append", dest="recipients", metavar="RCPT", type="string", help="Email recipient")
    parser.add_option('-t', '--test', action="store_true", dest="test", metavar="TEST", help="Run tests")
    parser.add_option('-v', '--verbose', action='store_const', dest='log_level', const=logging.DEBUG, help='Verbose output')
    parser.set_defaults(server="smtp.gmail.com", port=587, test=False, log_level=logging.INFO)
    (options, args) = parser.parse_args()
 
    _validate_options_and_args(parser, options, args)
    configure_logging(options.log_level)
 
    if options.test:
        _test() # Too lazy to write a test for this script.  @TODO - use mocks 
 
    # Retrieve URL and return html
    html = getPage(args[0])
 
    # Send mail with returned html as body 
    mail(options.from_email, options.recipients, 
         '%s @ %s' % (args[0], (datetime.datetime.now().strftime("%A %B %d %I:%M:%S %p %Y"))), 
         html, 'text/html', 
         server=options.server, port=options.port, username=options.username, password=options.password)
 
    # Return with appropriate exit code
    sys.exit(0)
 
def _test():
    import doctest
    doctest.testmod(sys.modules[__name__])
 
if __name__ == '__main__':
    main()

All right stop, cron time! (imagine a 90’s pop song)

As an added bonus, you can install this script to run via cron so you’ll magically end up with webpages archived in your inbox! Neat. You can read my previous post on cron, or you can create the following crontab.

MAILTO=ryankanno@CHANGE_TO_YOUR_EMAIL.com
# minute (0-59),
# |      hour (0-23),
# |      |       day of the month (1-31),
# |      |       |       month of the year (1-12),
# |      |       |       |       day of the week (0-6 with 0=Sunday).
# |      |       |       |       |       commands
  0      0       *       *       *      /usr/bin/python2.5 /PATH/TO/web2email.py -u GMAIL_USER -p GMAIL_PWD -f FROM_USER -r RECIPIENT URL

As a side note, don’t forget double quotes around URL if there’s spaces!

Notice, change the value of ryankanno@CHANGE_TO_YOUR_EMAIL.com to your email address (or comment the line out with a # if you don’t want emails sent to you), GMAIL_USER to your Google username, GMAIL_PWD to your Google password, FROM_USER to the from address in the mail header, RECIPIENT to the recipient email address, and URL to the URL you want backed up.

I know, I know. The critics.

The critics will say that your Gmail username and password are in cleartext. I know. They are. So… I’m hoping that since you just need an archive of a publicly available URL on the Internets, the data doesn’t need to be super-duper-Fort-Knox-protected. If it does, this script isn’t for you. :( Oh, yeah, before I forget… here’s a hint… *cough*create another Google account*cough*. With that said, archive to your heart’s content!

Enjoy!

Popularity: 33% [?]

Tagged: , , , , , .


Got Toes? The Vibram FiveFingers Review

Let me first preface this blog by stating that no, I’m not dead. I’ve been a little really busy with work. Don’t worry, I have a bunch of new blogs planned around all the useless pieces of software that I’m working on. Until then, check out some of my latest buys.

I finally succumbed to peer pressure.

Vibram FiveFingers
After some careful online research (like here, here, and here) and being persuaded by my co-worker Stephane, I finally mozied on down to get myself a pair of Vibram FiveFingers. After all, being named one of Time Magazine’s Inventions of the Year couldn’t be all that bad… right? :)

After perusing their website, I found only two retailers on Oahu that sold them.

  • Uyeda Shoe Store across from Puck’s Alley (map)
  • The Wheatgrass Center behind the Bank of Hawaii on Waialae (map)

After calling Uyeda’s several times and being greeted with one of those funky fax feedback tones each time (note – please update your online phone number), I finally decided to ring up The Wheatgrass Center. For those of you instructionally-impaired like myself, just take a left on Waialae Ave. before the Bank of Hawaii heading Kahala bound. Located behind the bank, The Wheatgrass Center is quite an interesting store selling both the Vibram FiveFingers and of course… wheatgrass. (Why people want to ruminate like cows is still truly beyond me.)

Review

When I first saw the Vibram FiveFinger, I did what any other (semi) normal human being would do – I laughed. Not any normal laugh, mind you – but a “there’s no f-in way I’m wearing that in public” laugh. They resemble footwear of a ninja-in-training, and since I’m neither of the two (a ninja or in training), I really couldn’t fathom seeing myself in a pair.

But after trying them on, I was immediately taken back; back to small kid time when I ran barefoot and carefree in the red dirt hills of Mililani. The Vibram FiveFingers not only allows you to feel the contour of the ground, but also provides protection to the soles of your feet. After being given the sales pitch by Mr. Fukuda, I was sold. There’s a few models; I ended up purchasing the Classic. (Check out their website to see the entire product line). Not to mention, Mr. Fukuda instructed ordered me to wear the pair out the door. After a full weekend’s worth of wear and tear, here’s a few images of them on my feet – along with a short list of my pros and cons.

Vibram flat on the kitchen floorVibram side profileVibram angleVibram overhead view

The Pros

  • It’s surprisingly comfortable. Sometimes my toes still feel weird being separated, but it’s pretty neat to actually feel the ground without fear of having a rock in your foot. Btw, if you like the separated toe thing, check these socks out from Injini.
  • If everything the Internet world says is true (like we all know it is!), I’ll have crazy leg/toe muscles, damnit! And, not to mention, it promotes a more natural walking motion. To learn more, read this article about barefoot running.

The Cons

  • I developed a blister on the back of my foot near my Achilles from the back strap. It’s pretty sore, but after reading other reviews, I’m sure I’ll get used to it.
  • Since I’m fairly self-deprecating and a non-fashionista (you should see my car), the design doesn’t faze me one bit – but I could see how embarrassment could set in.
  • The price. $73 bucks isn’t a drop in the bucket in this economy. Damn, do you know how many beers I’m giving up for this?

I’ll tell you in December how the Honolulu Marathon goes with these on!

Popularity: 44% [?]

Tagged: , , , , , , .


ERB block comments in RHTML templates using Ruby on Rails

This blog is simply a reminder to myself more than anything else.

After searching the great Googs and reading here, here, and here about commenting out regions in your RHTML templates, I couldn’t find a (good) solution aside from the (<... if false ...>) paradigm. Using Rails 1.2.3 (I know, I know… we’re old school), to get block comments in Rails, the following worked for me, but unfortunately, still wasn’t recognized by NetBeans 6.0 (Boo!).

1
2
3
4
5
6
7
8
9
10
11
12
13
<table>
  ...
  <tbody>
    <tr id="<%= "photographer_#{photographer.id}"%>">
      <td><% 
=begin %>
<%= h photographer.first_name unless photographer.blank? %><% 
=end %>
      </td>
    </tr>
  </tbody>
  ...
</table>

Happy Coding!

Popularity: 49% [?]

Tagged: , , , , , , , , , .


Powered by Wordpress. Stalk me.