Matthew Deiters
Email: mdeiters at gmail .com
RSS Feed
about.me
I am committed to "Individuals and interactions over processes and tools, Working software over comprehensive documentation, Customer collaboration over contract negotiation, Responding to change over following a plan"
...Agile ManifestoSearch.this.site
encase.c#.AOP.framework
Additional.links
Localizing Gems (vendor everything) with Native Extensions
Localizing all libraries used by a rails app is a good strategy to building robust systems. Localizing or 'vendor everything' mitigates version conflicts between applications and simplifies deployments as we move from one environment to another. It drastically increases the portability of our application and transparency of what we have dependencies on. Additionally it enables something I consider very important,checkout development, which essentially means a developer can check out the code from scm for the first time and be running the development environment...no lengthy "how to setup development environment" documents.
We localize all our dependencies (i.e. gems) in the enterprise environment since by nature we internally run our rails apps in a "shared hosting" environment and would frequently run into the above issues if we didn't localize. It is trival to localize pure ruby gems and there are even other gems to help us make it stupid easy to 'vendor everything', see http://gemsonrails.rubyforge.org/. Personally I don't find it that hard to just use the "gem unpack command" instead. But what about localizing gems that have native extensions...
Localizing Hpricot
If your rails app is doing any extensive XML processing you should avoid REXML as its terribly slow. Hpricot proves to be a nice XML parser for ruby but it depends on some c libraries. Since we localize everything we are going to need to figure out how to freeze the native extensions to our rails app. The tricky part of native extensions is they are unique to each OS/architecture. To set the stage we have to run our apps on several architectures:
- Windows
- Mac OSX Intel
- 32 Bit RedHat
- 64 Bit RedHat
1. Vendoring the gem
This is the easiest part, assuming you have the hpricot gem installed on you development box, run from the command line:
cd vendor gem unpack hpricot
You should now have the hpricot gem source in your vendor folder.
2. Building the native extension by hand
We need to understand the anatomy of a gem to do this next bit. Generally gems with native extensions will have a ext folder where the c source code and such live. When you typically install a gem the install process will compile these c source code and generate our ruby extension targeted for our particular OS Architecture and put the compiled library in under the lib folder of our gem...for example on mac the installed hpricot gem has a i686-linux folder with the compiled library under the hprict lib folder. But up until now we have only unpacked our gem to the vendor folder the native extension has not been built so our hprioct's lib folder will only have ruby source.
To build the native extension by hand we need to navigate the the C source code in the ext folder, create the make file, and then run the makefile to compile.
cd vendor/hpricot-0.6/ext/hpricot_scan ruby extconf.rb make
3. Moving the compiled library to the lib folder
If everything went smoothly above, then you will see a new file under the ext/hpricot_scan folder. On macs this is hpricot_scan.bundle. When you install a gem with "gem install hpriot" this file is moved for us to the lib folder, but because we are building by hand we need to move it. We want to move it to a folder under the vendor/hpricot/lib folder target for the architecture we just built. I do this by getting the OS platform as ruby sees it.
ruby -e "puts RUBY_PLATFORM"
On my mac, the output is "i686-darwin8.9.1" so I create a i686-darwin8.9.1 folder and put the hpricot_scan.bundle in this folder. I'll repeat step 2 (compiling the source files) above and create folders and copy the compiled library for each OS architecture (mac, various linux flavors, etc) I plan to run my rails app on. This might be a little time consuming if your app needs to run on 3 or 4 environments but once the libraries are built we can reuse this localized gem for all other rails projects. For our current project I have serveral folders with their own compiled library under the froozen hpricot's lib folder: i486-linux, i686-darwin8.9.1, i686-linux, x86_64-linux.
4. Configuring Rails
At this point we have our localized gem ready but we need to tell rails about it. In our envionrment.rb file we want to add:
config.load_paths << "#{RAILS_ROOT}/vendor/hpricot-0.6/lib"
config.load_paths << "#{RAILS_ROOT}/vendor/hpricot-0.6/lib/#{RUBY_PLATFORM}"
This first puts the froozen hpricot in our load paths so we load up the localized gem in vendor. The second line is so we load up the right library for the right OS platform we are running on.
Let me know if you have any issues/questions: mdeiters@gmail.com
Enterprise Capistrano: Deploying to Servers With Unique Credentials
Capistrano makes an assumption that all servers in your application stack share the same credentials. As much logical sense as this makes, in large enterprise landscapes it is often not a reality. The nature of this world (Enterprise) is that trying to shift the mindset of all shareholders (infrastructure, support teams, security, etc) and then changing long running practices is not always feasible. This is especially true when doing so on numerous severs servicing numerous other systems that depend on the already in place mismatched credentials. For us...we have our normal application servers and database servers that DO share matching credentials. But along with them we use a separate server env to store and host our assets (images, css, js) with its own credentials.
Still desiring to leverage capistrano for its core automation benefits we had to monkey-patch capistrano so we could supply server specific username and passwords. Drop this in your lib folder or somewhere else more relevant. WARNING: This is basically cut-paste from the capistrano internals with some added sugar - I take no responsibility for the class design and multiple responsibilities in this rather big method.
#This is a core class in the capistrano library. it is overridden so that we can support servers that have different user names and passwords
module Capistrano
class SSH
class << self
alias :original_connect :connect
def connect(server, options={}, &block)
special_server_settings = options[:server_authentication] ? options[:server_authentication][server.to_s] : nil
if special_server_settings.nil?
return original_connect(server, options, &block)
else
methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
password_value = nil
ssh_options = (options[:ssh_options] || {}).dup
ssh_options[:username] = special_server_settings[:user] || server.user || options[:user] || ssh_options[:username]
ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
begin
connection_options = ssh_options.merge(
:password => password_value,
:auth_methods => ssh_options[:auth_methods] || methods.shift
)
connection = Net::SSH.start(server.host, connection_options, &block)
Server.apply_to(connection, server)
rescue Net::SSH::AuthenticationFailed
raise if methods.empty? || ssh_options[:auth_methods]
password_value = special_server_settings[:password]
password_value = password_value.call if password_value.is_a? Proc
retry
end
end
end
end
end
end
Your capfile will look something similar to this:
role :app, 'thisapp.rocks.com'
role :web, 'thisapp.rocks.com'
role :db, 'thisdb.rocks.com'
role :assets, 'my.asset.server01.com'
set :user, 'the-primary-user'
set :server_authentication, { 'my.asset.server01.com' => {
:user => 'assetusr' ,
:password => 'password'}}
}
....
2007 Retrospective
I left ThoughtWorks a couple months ago and rejoined the ranks with independent contractors...since leaving TW I've had a chance to work in unique environments with Ruby and Rails. Where it seems the majority of Rails projects are product companies (i.e. startups, ecommerce and general web sites) I have for the most part worked with large organizations and now spend most of my time with a large global management firm (a.k.a "An Enterprise"). The obstacles and challenges are very different then the Rails sweet spot but they provide the opportunity to close the gaps ruby has from other platforms and fill a niche (and no I do not mean JRuby). I hope to roll out most of lessons learned into the community over this next year. The goal is to write useful content more frequently as I blogged very little in 2007 and wrote very little useful content for Ruby prior. The paucity of postings is mostly due to day to day work with my new clients and traveling:
- NY 6 times
- India
- France
- Thailand
- Australia
- Hawaii