tag:blogger.com,1999:blog-56930547449953130952024-02-19T10:37:29.232-05:00KangarooWho‽My viewpoint on the world at large — Yea, you can probably just ignore everything I say.Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.comBlogger53125tag:blogger.com,1999:blog-5693054744995313095.post-69035656996438811252015-03-30T11:30:00.000-04:002015-04-04T11:31:23.340-04:00AWS Cloud Formation Templating<h3>
AWS Cloud Formation</h3>
Amazon Web Services is a vast collection of utilities and systems that allow you to create almost anything you want "in the cloud". It really is great, however managing it all can be a real headache. <a href="http://aws.amazon.com/opsworks/" target="_blank">OpsWorks</a> helps quite a bit but when you need to do more complex things (i.e. use Windows servers) you have to step out of that box. <a href="http://aws.amazon.com/cloudformation/" target="_blank">Cloud Formation</a> is a tool that allows you to (almost) completely script your entire AWS configuration. Just build some JSON and sent it to CF and *bam* you have a whole system. Yea!<br />
<br />
Well, its not quite that simple. While CF lets you do most things in the AWS universe it still can't configure some things, like <a href="http://aws.amazon.com/codedeploy/" target="_blank">Code Deploy</a> and some of the more complex VPC settings. The other thing it lacks is real templating. Sure CF has some support for basic lookups in the same JSON template and even some lookups for external things (like AWS region) but its not really enough to build robust, layer templates. You end up writing something that looks like <a href="http://en.wikipedia.org/wiki/XSLT" target="_blank">XSLT</a> that resurrects nightmares from long ago. Boo!<br />
<br />
Enter <a href="http://handlebarsjs.com/" target="_blank">Handlebars JS</a>, the Javascript templating system. In this post I'll walk you through how I used Handlebars to create a CF templating system to handle building lots of Stacks in lots of AWS Regions. <br />
<h3>
<br /></h3>
<h3>
First things, first</h3>
<div>
When I was building my templates I realized that there is quite a bit of duplicate information that I needed to pull out. AMIs that each project might use (Windows Server Core, Ubuntu Linux, etc.) or IP addresses for Security Groups are in every template and updating this information everywhere was a pain and is subject to mistakes. I also wanted to have different types of stacks for different resources; Production, Development, Staging, etc. Again, there is duplicate information everywhere.<br />
<br />
My solution involves three main directories:<br />
<ul>
<li><b><span style="font-family: Courier New, Courier, monospace;">/common/...</span></b> this directory holds things that are common to all (or most) of the templates</li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>/projects/...</b> </span>this directory contains project specific template pieces</li>
<li><b><span style="font-family: Courier New, Courier, monospace;">/artifacts/...</span></b> this directory holds the finished, compiled templates ready to be pushed to Cloud Formation.</li>
</ul>
The three main types of files in the common directory are the global settings (global.json), the environment settings (stage.hbs), and the base template (base-instance.hbs). The global file contains things that are the same in all the templates (IP addresses, AMI Ids, etc.) Basically, anything that needs to be accessible from any template should go in this file.<br />
<script src="https://gist.github.com/rnhurt/a1263c6f83f05b0fe70a.js"></script><br />
The environment file contains things that are shared across a specific environment (Staging, Production, etc.) This includes things like names of VPCs, subnets, keypair names, etc.<br />
<script src="https://gist.github.com/rnhurt/d71156c0d2ce015da7f4.js"></script><br />
Finally, the base template is the structure upon which the final template is built. Every type of thing that you want to configure using CF (EC2 instances, AutoScaling Groups, Route53 Record Sets, etc.) has to be specified in this file.<br />
<script src="https://gist.github.com/rnhurt/05aba51470032c09356c.js"></script><br />
<h3>
Here's where it gets interesting</h3>
Now that we have some Handlebars & JSON files we need to turn them into Cloudfront templates. Here's a small Node program I wrote that reads each project file, combines it with an environment file and the global JSON. I included a couple of extra "helpers" to do things like change the case of strings and look up objects in an array by their "Id".<br />
<script src="https://gist.github.com/rnhurt/0a63f03e35495106357d.js"></script><br />
This is really my first substantial Node application so be gentle. :)<br />
<h3>
<br /></h3>
<h3>
Conclusion</h3>
</div>
<div>
So, you should now be able to build CF templates that abstract common things out into separate files and do fancy things like build all of your templates at once. Yeah! The best part is that this script can be run from a Continuous Integration server (like <a href="https://jenkins-ci.org/" target="_blank">Jenkins</a>) and the resulting templates automatically applied to CF.</div>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-6631312914881689542015-03-30T10:06:00.000-04:002015-03-30T10:06:33.364-04:00Jenkins slaves in AWSThis is just a short post on a problem that I was having running Jenkins in the Amazon cloud (AWS). Our setup consists of a master Jenkins server (running Amazon Linux) and a Jenkins slave (running Windows Server 2012 R2). The slave was connected to the master through an ELB (Elastic Load Balancer) so that if/when the master had to change IP addresses I wouldn't have to mess with any DNS changes. This turns out to be a bad idea.<br />
<br />
You see, Jenkins wants to keep an open TCP connection between the master and all the slaves. That way it can keep tabs on what's going on, schedule new jobs, etc. The problem with using an ELB is that it likes to close idle connections, by default after 60 seconds. The result is that I was getting this error in my slave logs just about every minute:<br />
<script src="https://gist.github.com/rnhurt/fc9bc796c22a4414c736.js"></script><br />
This is far from ideal, as when the slave disconnected it was causing any running jobs to fail. :( The solution was pretty simple; remove the ELB and create a Route53 record set pointing to the master and use that for all the slave communications. Ever since this simple change I've had few (if any) communications problems. Yeah! :)Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-14714383961712893672013-11-14T14:23:00.003-05:002014-07-21T09:14:17.567-04:00(Ab)using Chef in AWS OpsWorks.<h2>
<a href="http://aws.amazon.com/opsworks/" target="_blank">AWS OpsWorks</a></h2>
<div>
AWS OpsWorks is a wonderful tool that I'm growing more and more fond of every day. However, not being familiar with <a href="http://www.opscode.com/chef/" target="_blank">Chef</a> I've been hamstrung by what I can do with it. I wanted to be able to create a clean, simple layer but still retain the ability to deploy code automatically to it. The "other" layer type was perfect, except it didn't include any deployment tools (or other goodies). So, banging my head against the wall for a couple of days I have some tips to share.</div>
<h3>
<br /></h3>
<h3>
Tip #1 - Chef-Solo is globally scoped</h3>
<div>
OpsWorks uses Chef-Solo which is globally scoped. This means that there is no Chef server and you don't have to do any funky namespace things in your custom Chef recipes. I was trying to copy the OpsWorks Chef recipes into my custom cookbooks, and trying to use Git submodules, and all kinds of funky stuff. The simple answer is that you don't have to do any of that; just use it like its in your custom cookbook and it will work.</div>
<div>
<br />
<script src="https://gist.github.com/rnhurt/4cf1fbb2b7388685fc16.js"></script><br />
<h3>
Tip #2 - Drop the manafests</h3>
</div>
<div>
Just a small tip here, Chef-Solo doesn't use the <code>metadata.rb</code> file so there is no need to include it in your cookbook. Unless you really want to, that is.</div>
<h3>
<br /></h3>
<h3>
Tip #3 - Just do it!</h3>
<div>
You don't have to create your own custom cookbook if you don't want to. If you find something in the <a href="https://github.com/aws/opsworks-cookbooks" target="_blank">opsworks-cookbooks</a> that you want to use, say <code>opsworks_bundler</code>, then just include it in the Custom Chef Recipes section of the layer edit page:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp48yiXZO3s2fINX8oB_LOfVD_dpQ9MFVnFDJu6201CzXFvODoNKZqwK1hwJ8xMADE622DoYiYk17QdBGVQ3qYyjT7YDA1Zpy0QMCD_Us4B0JltnThQjgRJ_3hhh9Oz7cWvO73L-DGvg/s1600/opsworks-custom-chef.tiff" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="OpsWorks option screenshot" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp48yiXZO3s2fINX8oB_LOfVD_dpQ9MFVnFDJu6201CzXFvODoNKZqwK1hwJ8xMADE622DoYiYk17QdBGVQ3qYyjT7YDA1Zpy0QMCD_Us4B0JltnThQjgRJ_3hhh9Oz7cWvO73L-DGvg/s1600/opsworks-custom-chef.tiff" title="OpsWorks options" /></a></div>
<div>
<h2>
Wrapup</h2>
</div>
<div>
As with most of my blog posts, this one is written with future me in mind. If I don't write it down, I'll forget it. However, it will hopefully help someone else that is in my position of only knowing enough Chef to be dangerous. I really shouldn't have had to bang my head against this for so long. :/</div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-91862987096613349552013-04-12T14:35:00.000-04:002014-06-06T12:47:02.801-04:00Setting your own ID using AWS::Record::HashModel<h3>
<b>June 2014 Update: This no longer works with the newer versions of the AWS SDK. This information is retained merely for historical reasons and you should not use the code below. You have been warned.</b></h3>
<br />
In a recent project I needed to save things to DynamoDB and be able to use my own values for the ID. The problem is that by default <a href="http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Record/HashModel.html">AWS::Record::HashModel</a> sets the ID field for you automatically and you can't change it. I asked about it on the <a href="https://forums.aws.amazon.com/thread.jspa?messageID=442089#442089">official AWS forums</a> and the answer was that it can't be done. At least not yet.<br />
<br />
So I had to come up with a solution myself. It's not pretty, but <a href="http://en.wikipedia.org/wiki/Monkey_patch">Monkey Patching</a> does come in handy sometimes. So, for any others out there who are trying to solve the same problem, he's what I did.<br />
<ol>
<li>Create a <code>monkey_patch.rb</code> file somewhere in your project where it will be loaded after the main AWS Gem.</li>
<li>Add the following code to the <code>monkey_patch.rb</code> file:<br />
<br />
<pre class="brush:ruby">module AWS
module Record
module AbstractBase
module InstanceMethods
# Allow the user to override the ID
def populate_id
@_id = @_data['id'] || UUIDTools::UUID.random_create.to_s.downcase
end
end
end
end
end
</pre>
<br />
</li>
<li>Add an "id" field to your <code>AWS::Record::HashModel</code> object.<br />
<br />
<pre class="brush:ruby">class MyModel < AWS::Record::HashModel
string_attr :id
...
end
</pre>
<br />
</li>
<li>Now, whenever you call the create method on MyModel if you provide an ID in the hash then it will get used as the hash_key value. <br />
<br />
<pre class="brush:ruby">MyModel.create{:id => "some_value_that_I_make_up"}</pre>
</li>
</ol>
<br />
Two things to be aware of, though:<br />
<ol>
<li>You have to add some other attributes to the model before it will be persisted properly. Just having an "id" won't cause it to save properly.</li>
<li>It's up to you to ensure that you don't create multiple models with the same ID.</li>
</ol>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com1tag:blogger.com,1999:blog-5693054744995313095.post-48230318584242420632013-04-01T19:39:00.000-04:002013-04-01T19:39:50.471-04:00Rake task to deploy to AWS OpsWorks<h2>Introduction</h2>I've spent the day building a deploy Rake task for my AWS OpsWorks environment and I thought I would share it with you.<br />
<br />
My project is a pretty simple Sinatra application that is being hosted in Amzon's cloud environment (AWS) with the help of the OpsWorks tools. One of the things I wanted to do is to make it easy to deploy the application from the command line. I guess I've gotten lazy with Heroku's "deploy from a Git push" and I wanted something similar in my new environment. <br />
<br />
After working out a couple of kinks, it wasn't too difficult really. Here's how to set it up in your environment. Basically, you just need to create a couple of files and fill in some default values from your OpsWorks environment.<br />
<br />
The <code>opsworks.yml</code> file defines all the layer/app ID's in all the regions you want to deploy to.<br />
<pre class="brush:ruby"># config/opsworks.yml
us-east-1:
layer_id: "your-layer-id"
app_id: "your-app-id"
us-west-1:
layer_id: "your-layer-id"
app_id: "your-app-id"
</pre><br />
The <code>Rakefile</code> file defines the rake tasks needed to deploy your software. Just make sure that you have the 'aws-sdk' Gem installed and a valid AWS credentials. I'm using a <code>config/aws.yml</code> file but you can do whatever you like.<br />
<pre class="brush:ruby"># Rakefile
require 'rubygems'
require 'bundler'
Bundler.require if defined?(Bundler)
# Authenticate to AWS
AWS.config(YAML.load_file('config/aws.yml')['production'])
client = AWS::OpsWorks.new.client
desc "Deploy the app to the LIVE environment"
task :deploy do
regions = YAML.load_file('config/opsworks.yml')
deploy_options = {}
deploy_options[:command] = {name:'deploy'}
# Loop through each region
regions.each do |region, options|
deploy_options[:instance_ids] = []
deploy_options[:app_id] = options['app_id']
deploy_options[:comment] = "rake deploy from '#{Socket.gethostname}'"
instances = client.describe_instances({layer_id: options['layer_id']})
next if instances.nil? || instances.empty?
# Capture the details for each 'online' instance
instances[:instances].each do |instance|
if 'online' == instance[:status]
deploy_options[:instance_ids] << instance[:instance_id]
deploy_options[:stack_id] = instance[:stack_id]
end
end
puts "Deploying to #{deploy_options[:instance_ids].count} instances in the #{region.upcase} region..."
client.create_deployment deploy_options
end
end
</pre><br />
<h2>Usage</h2><br />
Just run <code>rake deploy</code> to deploy your code to any "online" instances that matches the regions/apps/layers in your config file.Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-30514722319778126072013-03-28T10:07:00.000-04:002013-05-14T23:02:18.474-04:00Integrating ELB into an OpsWorks stack<h3>
UPDATE: Amazon has now built <a href="http://aws.amazon.com/about-aws/whats-new/2013/05/14/aws-opsworks-supports-elb/">ELB capability</a> into OpsWorks, so you no longer need to use this workaround.</h3>
<br />
As you might have guessed by my recent posts I'm experimenting with the <a href="http://aws.amazon.com/opsworks/">AWS OpsWorks</a> platform. I have been generally very pleased with everything except its lack of integration with existing AWS tools, specifically <a href="http://aws.amazon.com/elasticloadbalancing/">Elastic Load Balancers</a>. OpsWorks does include an <a href="http://haproxy.1wt.eu/">HAProxy</a> stack that you can use to load balance your system, but it lacks several features that ELB has and I find valuable. <br />
<div>
<br /></div>
<div>
To start, ELB is just easier to setup and manage. Basically, you don't have to do anything at all with it, just create an ELB instance through the GUI and attach your instances to it. It also multiple provides SSL termination points and automatically scales based on load. To top it off, it's cheaper than running your own HAProxy stack through OpsWorks. :)</div>
<div>
<br /></div>
<div>
The problem is that OpsWorks doesn't provide a way to integrate with ELB, <a href="http://aws.typepad.com/aws/2013/02/aws-opsworks-one-week-report.html">at least not yet</a>. So you have to wire it up yourself. This is not too big of a deal if you're already familiar with the AWS command line tools and <a href="http://www.opscode.com/chef/">Chef</a>, which is what Amazon uses to manage their instances. Unfortunately, I'm not super familiar with these things so I had to blindly work my way through the setup. Here's how I did it.<br />
<br />
1) Create an ELB in the normal way and give it some name "my-project-lb"<br />
<br />
2) Create a custom Chef cookbook and check it into Github or store it in an S3 bucket or something. It should look something like this:<br />
<pre class="brush:shell">$ mkdir -p cookbooks/aws/recipes
$ touch cookbooks/aws/recipes/default.rb
$ touch cookbooks/aws/recipes/register.rb
$ touch cookbooks/aws/recipes/deregister.rb
</pre>
<br />
3) Edit the recipes in your cookbook to look something like this, replacing code as necessary:<br />
<pre class="brush:shell">## cookbooks/aws/recipes/default.rb
package "aws-cli" do
action :install
end</pre>
<br />
<pre class="brush:shell">## cookbooks/aws/recipes/register.rb
include_recipe "aws"
execute "register" do
command "aws --region #{node[:opsworks][:instance][:region]} elb register-instances-with-load-balancer --load-balancer-name my-project-lb --instances '{\"instance_id\":\"#{node[:opsworks][:instance][:aws_instance_id]}\"}'"
user "deploy"
end</pre>
<br />
<pre class="brush:shell">## cookbooks/aws/recipes/deregister.rb
include_recipe "aws"
execute "deregister" do
command "aws --region #{node[:opsworks][:instance][:region]} elb deregister-instances-from-load-balancer --load-balancer-name my-project-lb --instances '{\"instance_id\":\"#{node[:opsworks][:instance][:aws_instance_id]}\"}'"
user "deploy"
end</pre>
</div>
<br />
<div>
3) Add your new cookbook to the OpsWorks custom Chef cookbook in your Application stack.<br />
<br />
4) Finally, add your new recipes to the proper <a href="http://aws.amazon.com/opsworks/faqs/#lifecycle">stack lifecycle events</a>. I chose Configure to run the "aws::register" recipe and Shutdown to run the "aws::deregister" recipe.<br />
<br />
Now just sit back and enjoy your new, cheaper, easier-to-use auto-balancing OpsWorks system. :)</div>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com4tag:blogger.com,1999:blog-5693054744995313095.post-46805990875149191722013-03-22T07:40:00.000-04:002013-03-28T10:10:06.290-04:00Rack Applications under AWS OpsWorks<h2>
<a href="http://www.crunchdot.com/wp-content/uploads/2013/02/d89a43574fcopie.jpeg-800x295.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="118" src="http://www.crunchdot.com/wp-content/uploads/2013/02/d89a43574fcopie.jpeg-800x295.jpg" width="320" /></a>AWS OpsWorks</h2>
<div>
First a bit about <a href="http://aws.amazon.com/opsworks/">AWS OpsWorks</a>, it's Amazon's answer to operating your own datacenter. With OpsWorks you can do things like easily autoscale your systems by time or load, deploy your applications from <a href="https://github.com/">Github</a> (or <a href="http://aws.amazon.com/s3/">S3</a>), monitor your stack with <a href="http://ganglia.sourceforge.net/">Ganglia</a>, and a whole raft of other things.</div>
<h2>
Stacks</h2>
<div>
The OpsWorks architecture is designed in what they call "stacks". These stacks are just groups of components that work together to do whatever task you want them to do (i.e. provide a web service of some kind). The stack is broken up into layers with each layer having a unique job within the stack (load balancer, app server, DB server, etc.) </div>
<h2>
Rails (Rack) Layer</h2>
<div>
The layer I'm concerned with in this post is the application server layer. There are several different types of apps that are supported out of the box; PHP, Rails, Node, and static HTML. I wanted to run a <a href="http://www.sinatrarb.com/">Ruby Sinatra</a> application, which is close to a Rails app, but not quite. It turns out that the "Rails" layer is actually a "Rack" application and natively supports anything that runs under <a href="http://rack.github.com/">Rack</a>, such as Sinatra. :)</div>
<div>
<br /></div>
<div>
All you have to do to get your Rack application running in OpsWare is create these three files in your project and deploy your application just like normal. :)</div>
<br />
<pre class="brush:ruby">#Gemfile
=============================
source 'https://rubygems.org'
gem 'sinatra'
gem 'unicorn'
=============================
</pre>
<br />
<pre class="brush:ruby">#app.rb
=============================
require 'rubygems'
require 'sinatra'
get '/' do
"hello world!"
end
=============================
</pre>
<br />
<pre class="brush:ruby"># config.ru
=============================
require 'sinatra'
set :env, :production
disable :run
require './app.rb'
run Sinatra::Application
=============================
</pre>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com1tag:blogger.com,1999:blog-5693054744995313095.post-27555687800948100172013-03-18T20:48:00.000-04:002013-03-18T20:56:42.591-04:00Programming Choices<h3>
<a href="http://www.my-programming.com/wp-content/uploads/2011/09/what-is-programming-language.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="150" src="http://www.my-programming.com/wp-content/uploads/2011/09/what-is-programming-language.jpg" width="200" /></a>
Introduction</h3>
I frequently ask myself what languages are on top in my mind. In the past I've loved almost everything from PowerBuilder to Perl to Java. There are a couple of languages I've really disliked (looking at you Cobol) but I don't have to work with them much so its not much of a problem. Anyway, here's what I would use to build something today.<br />
<h3>
Things I would use</h3>
<h4>
Ruby</h4>
<a href="http://www.ruby-lang.org/">Ruby</a> is very solid in my mind and is my go-to language for almost any job. It's been criticized in the past as slow and awkward but those days are gone and today's Ruby is a joy to work with. I love the syntax of the language and how everything seems to be named properly. I rarely have to look up documentation as my first guess is usually correct. It's <a href="http://en.wikipedia.org/wiki/Read-eval-print_loop">REPL</a> (<a href="http://www.ruby-lang.org/en/documentation/quickstart/">irb</a> / P<a href="http://vimeo.com/26391171">ry</a>) is beyond compare and almost removes the need to debug. I once thought that nothing could rival the Java JAR ecosystem, but I think that Ruby Gems is more extensive, more modern, and beats it in almost every way that counts.<br />
<h4>
Go</h4>
<a href="http://golang.com/">Go</a> has really peaked my interest in the last 6 months or so. There are more and more people talking it up and several companies releasing major projects that are written in Go. The upsides are plenty, from a fresh, clean syntax, to the out-of-the-box tools (gofmt, gofix, etc.), to the performance. The community is very active and its backed by a major computing powerhouse (<a href="http://google.com/">Google</a>). Still, its a fairly new and immature language and I think you would be hard pressed to get your company to start building mission critical apps in it. Well, unless your company is forward thinking and willing to take some risks to get major awards.<br />
<h4>
Java</h4>
<a href="http://www.java.com/">Java</a> was in my toolbox for a long time but anymore I just really don't like using it. Sure, it's fast and can run almost anywhere and has lots of libraries, but it just leaves me empty. I think my feelings turned when Sun was sold to Oracle and I saw the writing on the wall. Since then it just seems like the world has abandoned the language, along with Oracle. The fact that most Java code is "enterprise"-ready doesn't really help matters. Most of the Java code I see in the wild is ultimately flexible and ready to be used for almost any purpose. It's also nested so deeply that it's extremely hard to follow the code logic and I'm afraid to touch it. That said, it's not really the languages fault that people write it that way<br />
<h3>
Don't even look at me</h3>
<h4>
PHP</h4>
<div>
What can I say about <a href="http://php.net/">PHP</a> that hasn't <a href="http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/">been said before</a>? I think it has uses in quick and dirty projects, but using it for anything of production value is just a fools errand. Worse than that I firmly believe that writing PHP is bad for your programmer brain. It teaches the wrong things and guides you down the wrong paths. Hell, they put a <a href="http://php.net/manual/en/control-structures.goto.php">"goto" operator</a> into the language in 2009! Jeff Atwood <a href="http://www.codinghorror.com/blog/2012/06/the-php-singularity.html">commented</a> that the way to make PHP go away is to provide better alternatives, and I believe that Ruby is finally becoming that alternative. Its easier to write, faster, and easier to deploy (Bundler). Hosting solutions (<a href="http://heroku.com/">Heroku</a>) and cloud providers (<a href="http://aws.amazon.com/opsworks/">AWS OpsWorks</a>) are building one button deploy systems that match or exceed PHP's.</div>
<h3>
Wrap-up</h3>
<div>
Look, my point is that there is no one solution for every job. Next time you start a project take a long hard look at the problem you are solving and pick the best tool. Ruby is great for small utilities, miscellaneous web based work and (with <a href="http://rubyonrails.org/">Rails</a>) the perfect solution for CRUD websites. It even works for game programming and writing <a href="http://www.rubymotion.com/">iOS applications</a>. I find Go a perfect fit for heavy duty server systems (image processing, number crunching, etc.) Do what fits your job and makes you happy.</div>
Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-67244363780859352592010-10-12T14:39:00.000-04:002010-10-12T14:39:40.192-04:00Resizing a VirtualBox guest hard driveRecently, I needed to expand the hard drive on my Ubuntu VirtualBox guest machine from 8GB to 20GB. At first it looked like I was going to have to start from scratch, but a little Googling turned up this gem (which I stole from the <a href="http://forums.virtualbox.org/viewtopic.php?f=1&t=364&sid=0f6c731729f2cc73f48c473d11710606&start=90">VirtualBox forums</a>)<br />
<blockquote>#VBoxManage createhd -filename new.vdi --size 20000 --remember<br />
#VBoxManage clonehd old.vdi new.vdi --existing</blockquote>After the clone operation gets finished you just need to replace the old hard drive with the new on in VirtualBox and start the guest instance. The only thing that tripped me up is that the guest didn't immediately "see" the bigger hard drive. Well, to be more precise the hard drive was bigger but the partition was still set to 8GB. As quick boot into GParted did the trick and now I have room to move again on my VM. :)Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-57072986070539300922010-01-04T14:11:00.005-05:002010-01-26T13:45:51.357-05:00Authlogic: Restricting simultaneous sessionsI'm building a Rails based application for a client, which they are in-turn licensing to their customers on a per-seat basis. This means that they don't want multiple people using the same login at the same time; if you have 5 people in your shop, you have to purchase 5 licenses. Makes sense to me.<br />
<br />
Anyway, we're using the splendid <a href="http://github.com/binarylogic/authlogic">Authlogic</a> plug-in for all the authentication duties and while it does many things very well it doesn't have a built-in way to restrict simultaneous logins. I asked around and got a couple of tips on what might work and how I might proceed. It was a simple process but after several others had the same problem I thought I would formalize the "solution" in a blog post in hopes that it might help someone else in the future.<br />
<br />
<ul><li>Step One - Add a place to store a session key<br />
<pre name="code" class="brush:ruby"># add_session_key_to_users.rb
class AddSessionKeyToUsers < ActiveRecord::Migration
def self.up
add_column :users, :session_key, :string
end
def self.down
remove_column :users, :session_key
end
end
</pre>
This gives us a place to store a "session_key" value that changes every time a user logs in.
</li>
<li>Step Two - Insert the "session_key" on login<br />
<pre name="code" class="brush:ruby"># user_sessions_controller.rb
def create
...
if @user_session.save
# Save the session ID to detect simultaneous login attempts
@user_session.record.session_key = session[:session_id]
@user_session.record.save!
...
end
end
</pre><br />
What this does is forces the session_id to be saved to the User model each time a user logs into the site. We'll check this value later to make sure the session hasn't changed. I'm using the session_id here as a "unique" value but it could be anything you want; timestamp, IP address, etc...<br />
</li>
<li>Step Three - Make sure the user is unique<br />
<pre name="code" class="brush:ruby"># application_controller.rb
def current_user
...
# Prevent simultaneous logins
if @current_user && @current_user.session_key != session[:session_id]
flash[:notice] = 'Access denied. Simultaneous logins detected.'
current_user_session.destroy
end
end
</pre><br />
Here is where the rubber meets the road, so to speak. In Authlogic, the current_user method is accessed on every page request so it is the perfect place to check for duplicate user sessions. We simple verify that the session_id in the users cookie is the same one in the database. If they are different we destroy the session and update the flash message.<br />
</li>
</ul><br />
<br />
This is a pretty simple little hack but it seems to work OK. One "problem" that I have noticed is that while the session is immediately destroyed the current request continues unabated. This means that the two users could possibly perform two actions at the same time, but it shouldn't be a problem. Another side effect of this technique is that the last user to log in always gets the session. This might be a problem for you but it wasn't for my project.Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com5tag:blogger.com,1999:blog-5693054744995313095.post-53372552373998560062009-12-24T12:14:00.006-05:002010-01-26T13:46:29.197-05:00Switching Ruby platforms on Debian<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.debian.org/logos/openlogo-nd-50.png"><img style="float: right; margin: 0pt 0pt 10px 10px; cursor: pointer; width: 50px; height: 61px;" src="http://www.debian.org/logos/openlogo-nd-50.png" alt="" border="0" /></a><br />
My favorite Linux server distribution is <a href="http://www.debian.org/">Debian</a> and I use it whenever possible. Its sane layout and APT system make working with it easy and fun. I've also been doing quite a bit of <a href="http://rubyonrails.org/">Ruby on Rails</a> coding recently and I have grown to love the way it works and how easy it is to get massive amounts of work done. The problems start when you try to use Ruby on Debian. Sure, Debian tries to make things super easy to install and upgrade by using the APT package manager. However, the Ruby community insist on using Gem as its package manager and the two don't work and play well together.<br />
<br />
I recently wanted to try out the latest Ruby and found that while you can install both Ruby 1.8 and Ruby 1.9.1 they don't use the Alternatives system built-in to Debian. This means that the Ruby 1.8 executable is "hard-coded" into the system and even installing Ruby 1.9.1 doesn't change the default version that runs.<br />
<br />
One solution is to go through the system looking for links to the 1.8 stuff and change it to point to 1.9.1. To do this manually is tedious, error prone, and not reproducible; wouldn't it be great if there was a way to make all these links automatically? Enter the Debian alternatives system. Thanks to a <a href="http://groups.google.com/group/rails-oceania/msg/dc0f32bec9f49025">post by David Lee</a> I had a starting point to get my system working the way I wanted it to. His post was for Ubuntu and specifically targeted Ruby 1.9 so I've updated it a bit and laid out the steps below for your convenience.<br />
<br />
<ol><li>Install the latest versions for Ruby1.8 and Ruby1.9.1<br />
<pre name="code" class="brush:ruby">sudo aptitude install -s ruby1.8 riby1.9.1 irb1.8 irb1.9.1 ri1.8 ri1.9.1
</pre><br />
</li>
<li>Next we're going to configure the Alternatives system. Create a new file on your system and copy this code into it.<br />
<pre name="code" class="brush:ruby"># Filename: ruby-alternatives.sh
# install ruby1.8 & friends with priority 500 and make them the default
update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.8 500 \
--slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \
/usr/share/man/man1/ruby.1.8.gz \
--slave /usr/bin/ri ri /usr/bin/ri1.8 \
--slave /usr/bin/irb irb /usr/bin/irb1.8
# install ruby1.9.1 & friends with priority 400
update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9.1 400 \
--slave /usr/share/man/man1/ruby.1.gz ruby.1.gz \
/usr/share/man/man1/ruby.1.9.1.1.gz \
--slave /usr/bin/ri ri /usr/bin/ri1.9.1 \
--slave /usr/bin/irb irb /usr/bin/irb1.9.1
</pre><br />
</li>
<li>Now execute the script you just created to install your new configuration. You should see some messages go by but don't worry if it complains a little bit about the old 1.8 stuff not being there.<br />
<pre name="code" class="brush:ruby">sh ruby-alternatives.sh
</pre><br />
</li>
<li>You are now ready to alternate between Ruby installations on your Debian box. Just run the command below whenever you want to switch between 1.8 and 1.9.1.<br />
<pre name="code" class="brush:ruby">update-alternatives --config ruby
</pre></li>
</ol><span style="font-weight: bold;font-family:lucida grande;" >NOTE: </span><span style="font-weight: bold; font-style: italic;font-family:lucida grande;" >These commands worked on my Debian Squeeze install. Your mileage may vary.</span>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com7tag:blogger.com,1999:blog-5693054744995313095.post-8114737459589743192009-10-25T20:20:00.002-04:002009-10-25T20:27:54.640-04:00Flash video player stutters and drops frames...Why, oh why do Flash video players not work properly? I mean, I'm on a reasonably powerful Mac Book Pro running the latest browsers and Flash plug-in and <span style="font-weight: bold;">still</span> YouTube videos are almost unwatchable (for reasons other than the content :). Can no one save us from the horror that is Flash video? Am I missing something somewhere that makes video smooth and watchable? The audio works just fine but the video breaks and jumps like crazy.<br /><br />Its not a buffering issue, because no matter where I view it (home DLS, work T1) it still has the same performance. I have also fully buffered the videos before viewing them and even that didn't help things. Its also not a Mac problem as the same thing happens under Linux (no surprise) and Windows.<br /><br />I realize that HTML 5 has a video tag built-in, but I don't think I can wait that long. :/Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-34771007614226018442009-10-20T09:24:00.003-04:002009-10-20T09:49:25.298-04:00The International Council of Manlaws, Ltd.I found this in an old email today and thought I would repost. :)<br /><hr width="75%"><br /><br /><h3>The International Council of Manlaws, Ltd.<br /></h3><ol><li>Under no circumstances may two men share an umbrella.</li><br /><li>It is OK for a man to cry ONLY under the following circumstances:<br /><ol style="list-style-type: lower-alpha;"><li>When a heroic dog dies to save its master.<br /></li><li>The moment Angelina Jolie starts unbuttoning her blouse.<br /></li><li>After wrecking your boss's car.<br /></li><li>When she is using her teeth.</li></ol></li><br /><li>Any man who brings a camera to a bachelor party may be legally killed and eaten by his buddies.</li><br /><li>Unless he murdered someone in your family, you must bail a friend out of jail within 12 hours.</li><br /><li>If you've known a man for more than 24 hours, his sister is off limits forever unless you actually marry her.</li><br /><li>Moaning about the brand of free beer in a mate's fridge is forbidden. However complain at will if the temperature is unsuitable.</li><br /><li>No man shall ever be required to buy a birthday present for another man. In fact, even remembering your mate's birthday is strictly optional. At that point, you must celebrate at a strip bar of the birthday boy's choice.</li><br /><li>On a road trip, the strongest bladder determines pit stops, not the weakest.</li><br /><li>When stumbling upon other guys watching a sporting event, you may ask the score of the game in progress, but you may never ask who's playing.</li><br /><li>You may flatulate in front of a woman only after you have brought her to climax. If you trap her head under the covers for the purpose of flatulent entertainment, she's officially your Girlfriend.</li><br /><li>It is permissible to drink a fruity alcohol drink only when you're sunning on a tropical beach ... and it's delivered by a topless model ... and it's free.</li><br /><li>Only in situations of moral and/or physical peril are you allowed to kick or punch another guy in the nuts.</li><br /><li>Unless you're in prison, never fight naked.</li><br /><li>Friends don't let friends wear Speedos. <span style="font-style: italic;">Ever.</span> Issue closed.</li><br /><li>If a man's fly is down, that's his problem, you didn't see anything.</li><br /><li>Women who claim they "love to watch sports" must be treated as spies until they demonstrate knowledge of the game and the ability to drink as much as the other sports watchers.</li><br /><li>A man in the company of a hot, suggestively dressed woman must remain sober enough to fight.</li><br /><li>Never hesitate to reach for the last beer or the last slice of pizza, but not both, that's just greedy.</li><br /><li>If you compliment a man on his six-pack, you'd better be talking about his choice of beer.</li><br /><li>Never join your girlfriend or wife in discussing a friend of yours, except if she's withholding sex pending your response.</li><br /><li>Phrases that may NOT be uttered to another man while lifting weights:<br /><ol style="list-style-type: lower-alpha;"><li>Yeah, Baby, Push it!<br /></li><li>C'mon, give me one more! Harder!<br /></li><li>Another set and we can hit the showers!</li></ol></li><br /><li>Never talk to a man in a bathroom unless you are on equal footing: i.e., both urinating, both waiting in line, etc. For all other situations, an almost imperceptible nod is all the conversation you need.</li><br /><li>Never allow a telephone conversation with a woman to go on longer than you are able to have sex with her. Keep a stopwatch by the phone. Hang up if necessary.</li><br /><li>It is acceptable for you to drive her car. It is not acceptable for her to drive yours.</li><br /><li>Thou shalt not buy a car in the colors of brown, pink, lime green, orange or sky blue.</li><br /><li>The girl who replies to the question "What do you want for Christmas?" with "If you loved me, you'd know what I want!" gets an XBox 360. End of story.</li><br /><li>There is no reason for guys to watch Ice Skating or Men's Gymnastics. Ever.</li><br /><li>We've all heard about people having guts or balls. But do you really know the difference between them? In an effort to keep you informed, the definition of each is listed below:<br /><dl><br /><dt>"GUTS"</dt><dd>is arriving home late after a night out with your mates, being assaulted by your wife with a broom, and having the guts to say, "are you still cleaning or are you flying somewhere?"</dd><br /><dt>"BALLS"</dt><dd>is coming home late after a night out with you mates smelling of perfume and beer, lipstick on your collar, slapping your wife on the arse and having the balls to say, "You're next!"</dd></dl></li><br /></ol><br /><address><br />I hope this clears up any confusion, The International Council of Manlaws, Ltd<br /></address>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-89233958542469258572009-09-06T07:44:00.006-04:002009-09-06T08:05:47.722-04:00Debian Squeeze and an old Dell PE1650<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.debian.org"><img style="float: right; margin: 0pt 0pt 10px 10px; cursor: pointer; width: 100px; height: 123px;" src="http://www.debian.org/logos/openlogo-100.png" alt="Debian logo" border="0" /></a>I recently had to put Debian testing (Squeeze) on an old Dell PowerEdge 1650 server. Here's a little tip to save you some time if your ever in the same situation: upgrade the BIOS first!<br /><div style="text-align: justify;"><br />Its kind of a long story but my original PE1650 was running BIOS A10 when it died due to memory slot failure. So I borrowed a friend's unused PE1650 until I could get a "new" one on-site. Well, the new one was even older than mine and was running BIOS A05. I didn't think it was a big deal and installed Lenny (stable) and then upgraded to Squeeze (testing). And then the problems began. Let's just say that I spent a week of sleepless nights and frustrating days trying to figure out why the machine would just suddenly, and without any notice at all, freeze. And it wasn't your normal hang either as there was no kernel panic messages, no messages in the syslog, not even anything on the console.<br /><br />I upgraded the BIOS to A11 (the latest available) and rebooted the machine; its been running now for almost 12 hours. This post may be a little premature as I've thought the problem was solved several times before, but everything was pointing to a hardware failure and the BIOS qualifies as hardware in my book. Regardless, I am still a little gun shy and will feel much better when it stays running for at least a whole day. A week would be better. :)<br /></div>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com1tag:blogger.com,1999:blog-5693054744995313095.post-15304305037592222992009-08-23T10:32:00.008-04:002009-08-23T11:10:34.902-04:00Ruby-AAWS & RailsOn August 15, 2009 Amazon begin requiring all calls to their AWS servers to be cryptographically signed. This caused <a href="http://mynastuff.com/">MynaStuff</a>, our home inventory website, to gradually break and stop serving pages. This post is to help me remember details about what I did this weekend and to help anyone else out who might be in the same boat.<br /><br />Because we use memcached on our servers the actual degradation of the service was quite slow and intermittent. However it soon became clear that we had a major problem and after a few seconds of investigation I knew what I had to do. We had built MynaStuff pretty quickly and had just thrown together a home grown connection to the AWS service. It was time to either add crypto signing to our code or use someone elses code. Never a big fan or build over buy I downloaded <a href="http://www.caliban.org/ruby/ruby-aws/">Ruby-AAWS</a> and started hacking.<br /><br />I can't say enough good things about Ruby-AAWS, it was easy to install and pretty easy to work with. Even though it wasn't designed to work with Ruby on Rails everything went pretty smoothly until I tried to get the data out of the memcache. The problem centered around how Ruby-AAWS builds classes on the fly to contain the data coming back from AWS. The way it works is really slick, however Ruby doesn't like the virtual classes too much so AAWS has to provide a factory method (of sorts). Just a simple call to Amazon::AWS::AWSObject.load() with the string that you retrieved from your memcache system and your good to go. Or so I thought. <br /><br />It turns out that Ruby-AAWS depends on the marshal.load() method to throw an ArgumentError when it has a problem reading some data and Rails somehow changes that exception to something else. So, no matter what I tried it always failed. The good thing about Open Source code is the ability to find out exactly what is wrong and then fix it (for your system or for the whole world). After changing the aws.rb file to check for any exception (i.e. Exception) the code worked again. However, checking for any exception is like killing a fly with a sledgehammer and is not a good idea. I am currently looking for the reason that the exception thrown by marshal.load() changed and how I can fix me code to accept this and deal with it.<br /><br />Anyway, here is the patch I submitted to the Ruby-AAWS author. Happy hunting!<br /><br /><pre name="code"><br />*** aws.rb.ORIG 2009-08-22 14:44:39.000000000 -0400<br />--- aws.rb 2009-08-22 14:44:51.000000000 -0400<br />***************<br />*** 230,236 ****<br /> def AWSObject.load(io)<br /> begin<br /> Marshal.load( io )<br />! rescue ArgumentError => ex<br /> m = ex.to_s.match( /Amazon::AWS::AWSObject::([^ ]+)/ )<br /> const_set( m[1], Class.new( AWSObject ) )<br /> <br />--- 230,236 ----<br /> def AWSObject.load(io)<br /> begin<br /> Marshal.load( io )<br />! rescue Exception => ex<br /> m = ex.to_s.match( /Amazon::AWS::AWSObject::([^ ]+)/ )<br /> const_set( m[1], Class.new( AWSObject ) )<br /><br /></pre>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-70222991093453502562009-07-23T09:28:00.009-04:002010-01-26T13:47:14.122-05:00Backing up your database with logrotateI use <a href="http://www.boxbackup.org/">BoxBackup</a> in lazy mode to back up my systems. This works well for things like normal files and directories but not so well for databases. I wanted to periodically dump my database to a file and then have BoxBackup come along and do its thing. The only problem was how to name each backup file and how to clean up after so many backups had been done. Since it runs in lazy mode I can't know ahead of time exactly when the backup will happen and which files it will backup.<br />
<br />
I looked at generating files with timestamps and then doing some calculations about cleaning up old files but that just seemed messy and I was sure that someone must have run into this type of situation before. Then I thought about log files and how they are backed up. It turns out the the standard Linux <code>logrotate</code> command knows how to rename files and get rid of old ones automagically. It even has script functionality that allows you to run commands before and after the rotate happens. Sounds perfect. Here's what I did.<br />
<br />
First I created a directory in my web apps directory to hold all the database information and backup files. I'm using PostgreSQL, but you could do the same thing for MySQL or even Oracle. Just change the names of the directories and commands to something more suitable for your environment.<br />
<br />
<pre name="code" class="brush:shell">$ mkdir /var/www/postgres
</pre><br />
<br />
Next we have to create a status file for <code>logrotate</code> to use. Since we will be running this as a non-root user we don't want to pollute the system status file with our non-log database backup stuff.<br />
<br />
<pre name="code" class="brush:shell">$ touch /var/www/postgres/logrotate.status
</pre><br />
<br />
We also want to create a "fake" dump file so that <code>logrotate</code> will have something to start working with. If this file doesn't exist <code>logrotate</code> will refuse to rotate it. In this case we are backing up a Postgres database called "mydatabase".<br />
<br />
<pre name="code" class="brush:shell">$ touch /var/www/postgres/mydatabase.dump
</pre><br />
<br />
Now we get to the heart of the matter; the logroate script itself. This tells <code>logrotate</code> to keep 10 copies of our dump but don't compress it and dont copy it. It will also complain if there is no original file to backup and it will create a blank "dump" for next time.<br />
<br />
<pre name="code" class="brush:shell"># /var/www/postgres/logrotate.pg_dump
# Dump PostgreSQL databases and prepare them for backup
/var/www/postgres/mydatabase.dump {
rotate 10
nomissingok
create
nocompress
nocopy
prerotate
test -x /usr/bin/pg_dump || exit 0
/usr/bin/pg_dump mydatabase -F c > /var/www/postgres/mydatabase.dump
endscript
}
</pre><br />
<br />
One final step and we're done. The <code>pg_dump</code> command connects to the database as the user who runs it and the postgres user has access to all the databases by default (on Debian anyway). So running the <code>logrotate</code> command as the postgres user allows us to backup any database on the system without a password. And in order to allow the postgres user access to our new setup we have to change the owner on the files in /var/www/postgres.<br />
<br />
<pre name="code" class="brush:shell">$ chown -R postgres:postgres /var/www/postgres
</pre><br />
<br />
Now, whenever you want to generate a new database dump just run the following command. You can put this is a crontab or just run it by hand. Remember to use the <code>-f</code> flag to force the backup to happen. <br />
<br />
<pre name="code" class="brush:shell">$/usr/sbin/logrotate -f /var/www/postgres/logrotate.pg_dump -s /var/www/postgres/logrotate.status
</pre>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-24526027402932198482009-07-17T23:00:00.000-04:002009-07-17T23:01:15.913-04:00Generating PDFs in Rails using PrawnIn my current Rails projects (<a href="http://github.com/rnhurt/gradesheet">Gradesheet</a> & <a href="http://mynastuff.com">MynaStuff</a>) I have the need to create reports as PDFs. Prawn is a relatively new library that does just what I need in pure Ruby. There are a couple of really good tutorials on how to integrate Prawn into your Rails project on the web but I wanted to do something a little different. There's even a plugin (<a href="http://www.cracklabs.com/prawnto">PrawnTo</a>) that makes Prawn look like a native Rails construct and allows you build PDFs without leaving your views. Neat!<br /><br />However, this is not really how I wanted to implement reporting in my applications. So I created a directory in lib and filled it with self-contained reports. In my view, this makes reports easier to build and maintain. Let's get started. First, you need a Rails project.<br /><br /><pre name="code" class="brush:ruby"><br />strutter$ rails HelloPrawn<br /> create <br /> create app/controllers<br /> ...<br /> create log/server.log<br /> create log/production.log<br /> create log/development.log<br /> create log/test.log<br /></pre><br />Now, we need to get the Prawn & Prawn-layout plugins. Since they are pretty new and constantly changing I like to get the latest version straight from GitHub. Just clone them in the usual way. Note that you have to get a couple of subcollections when you get Prawn this way.<br /><pre name="code" class="brush:ruby"><br />$cd vendor/plugins<br />$git clone git://github.com/sandal/prawn.git<br />Initialized empty Git repository in /Rails/HelloPrawn/vendor/plugins/prawn/.git/<br />remote: Counting objects: 6716, done.<br />remote: Compressing objects: 100% (2762/2762), done.<br />Receiving objects: 21% (1459/6716), 6.31 MiB | 220 KiB/s <br />...<br />$git clone git://github.com/sandal/prawn-layout.git<br />Initialized empty Git repository in /Rails/HelloPrawn/vendor/plugins/prawn-layout/.git/<br />remote: Counting objects: 271, done.<br />remote: Compressing objects: 100% (232/232), done.<br />remote: Total 271 (delta 111), reused 0 (delta 0)<br />...<br /></pre><br />Since we are running Prawn from Git we have to do a couple of extra things.<br /><pre name="code" class="brush:ruby"><br />$cd vendor/plugins/prawn<br />$git submodule init<br />Submodule 'vendor/pdf-inspector' (git://github.com/sandal/pdf-inspector.git) registered for path 'vendor/pdf-inspector'<br />Submodule 'vendor/ttfunk' (git://github.com/sandal/ttfunk.git) registered for path 'vendor/ttfunk'<br />$git submodule update<br />Initialized empty Git repository in /Users/richardhurt/Rails/HelloPrawn/vendor/plugins/prawn/vendor/pdf-inspector/.git/<br />remote: Counting objects: 16, done.<br />remote: Compressing objects: 100% (14/14), done.<br />...<br /></pre><br />Now we have to tell Rails to load Prawn-layout as well as Prawn. I do this with a simple initializer.<br /><pre name="code" class="brush:ruby"><br /># config/initializers/prawn.rb<br />require "prawn/core"<br />require "prawn/layout"<br /></pre><br />Next we'll create a report. First build a new directory to hold all your reports.<br /><pre name="code" class="brush:ruby"><br />mkdir lib/reports<br /></pre><br />Then add this new path to your Rails load_path. Look for the following section in you config/environment.rb file and make it look similar to this.<br /><pre name="code" class="brush:ruby"><br /># Add additional load paths for your own custom dirs config.load_paths += %W({RAILS_ROOT}/extras )<br />config.load_paths += %W( #{RAILS_ROOT}/lib/reports )<br /></pre><br />and add a new PDF mime type.<br /><pre name="code" class="brush:ruby"><br />#config/initializers/mime_types.rb <br />Mime::Type.register_alias "application/pdf", :pdf<br /></pre><br />Now that most of the setup is out of the way we're ready to get to work. Let's create a simple report and then show the controller that goes along with it. This report doesn't do much but it shows the basics.<br /><br /><pre name="code" class="brush:ruby"><br /># lib/reports/hello_prawn.rb<br />class HelloPrawn<br /><br /> # Build the parameter screen for this report.<br /> def self.get_params()<br /> # This report has no parameters<br /> end<br /><br /> # Build the report in PDF form and sent it to the users browser<br /> def self.draw(params)<br /> # Create a new document<br /> pdf = Prawn::Document.new(:page => "LETTER")<br /><br /> # Make it so we don't have to use pdf. everywhere. :)<br /> pdf.instance_eval do<br /> # Print something<br /> text "Hello Prawn", :align => :center, :size => 20<br /><br /> # Render the page and send it back to the controller<br /> render<br /> end<br /><br /> end<br /> end<br /></pre><br />Finally we'll build a controller to either build the parameter screen or render the PDF itself.<br /><pre name="code" class="brush:ruby"><br />class ReportsController < ApplicationController<br /> <br /> def show<br /> # Since we are building the report object on the fly we need to make sure<br /> # that it is a valid report before we try to build/show it.<br /> begin<br /> # Try to make the report into an 'object'<br /> @report = params[:id].classify.constantize<br /> rescue NameError<br /> # This is not a valid report or some other error happened<br /> flash[:error] = "Unknown Report '#{params[:id]}'"<br /> redirect_to :action => :index<br /> else<br /><br /> respond_to do |format|<br /> format.html do<br /> # display the parameters screen<br /> end<br /><br /> format.pdf do<br /> # Send the PDF back to the browser for viewing<br /> send_data @report.draw(params),<br /> :filename => params[:id].titleize + '.pdf',<br /> :type => 'application/pdf',<br /> :disposition => 'inline'<br /> end<br /> end<br /> end<br /> end<br />end<br /></pre><br />Lets test it out. Start up your Rails server and go to <a href="http://localhost:3000/reports/hello_prawn.pdf">http://localhost:3000/reports/hello_prawn.pdf</a> and you should get an inline PDF report in return. The cool thing about this is that each report can generate its own parameters screen on demand. See that self.get_params() function up in the report? That's where you can put your ERB stuff and have it render when you hit <a href="http://localhost:3000/reports/hello_prawn">http://localhost:3000/reports/hello_prawn</a>. Neato!<br /><pre name="code" class="brush:ruby"><br /> def self.get_params()<br /> # Get the homeroom information<br /> homerooms = Student.find_homerooms()<br /> <br /> # Build the parameter screen<br /> params = <<-EOS<br /><br /> <form action="/reports/student_roster.pdf" method="get"><br /> <fieldset><br /> <legend>Student Roster</legend><br /> <br /> <label>Homeroom</label><br /> <select name='homeroom'><br /> <option value=''>ALL</option><br />EOS<br /><br /> # Add the homerooms<br /> homerooms.each do |homeroom|<br /> params += "<option value='#{homeroom}'>#{homeroom}</option>"<br /> end<br /><br /> params += <<-EOS<br /> </select><br /> <br /> <div class="spacer"><br /> <input class="btn positive" type="submit" value="Run Report" /><br /> </div><br /> </fieldset><br /> </form><br />EOS<br /></pre>Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com12tag:blogger.com,1999:blog-5693054744995313095.post-12065585300219675862009-05-21T05:09:00.008-04:002009-05-21T14:57:47.907-04:00Why would I give up my iPhone for a Palm Prē?With the recent announcement that the <a href="http://www.engadget.com/2009/05/19/palm-launching-pre-on-june-6th-official/">Palm Prē is going on sale June 6th</a> for <strike>$300</strike> $200, I am very excited and can't wait to try it in the store. I currently own a 1st generation iPhone and I have to say that I have become disillusioned with it. Sure it does some things really well (dialing a number from a map search) but other things are a complete disaster (integrating with online apps, ToDo lists). I've realized that the iPhone is nothing but a computer accessory and it will always require a computer to be useful. This is something that I don't really want or need right now.<br /><br />I want a portable computer in its own right; something that doesn't need a tether to a desktop to work. The iPhone is completely dependent on its host system for important things like system updates. This is where I really miss my T-Mobile Sidekick (aka Danger HipTop). Everything it did was OTA (Over The Air); from system updates, to syncing. Heck you could even lose your phone and not worry about your data because it was stored on the web! It didn't do everything perfectly (the online web interface was atrocious) but it got close.<br /><br />Some features that I'm looking forward to on the Prē are OTA updates, online syncing, and background apps. After using Google calendar quite a bit I've realized that it's much better than Apples iCal program. Google makes it easier to have multiple calendars, share information with others, and generally navigate the system. I also love to use SSH to manage my servers, but the iPhone makes this hard if not impossible because of the lack of background apps. When I switch out of my SSH program to check an email, the iPhone closes the SSH app thus terminating my connection. Not cool.<br /><br />One thing I wont miss on my iPhone is the music playing function. For one, I already have an iPod that works really well as a music player. But the biggest problem is that the iPhone makes a terrible iPod. The original intent of the iPod was to make it very simple to listen to your music and the iPhone screws that up royally. Its very confusing to control the music playing in the background while at the same time switching between other applications. For some reason I can't seem to do it. And forget about playing online music while doing something else (see the part about background apps). I was really looking forward to Pandora Radio or Last.Fm on my iPhone when it was first announced only to be disappointed.<br /><br />I'm not saying that the Palm Prē will be the end all be all system for me. Its not even been released yet. But I think it will come close and I am looking forward to giving it a shot.Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com10tag:blogger.com,1999:blog-5693054744995313095.post-31896978006990613372009-03-16T09:58:00.006-04:002009-03-16T10:40:41.714-04:008 iPhone Faults & Missing FeaturesI've compiled a list of things I think the iPhone fails at but could be easily corrected. "Easily" in this case means no hardware fixes, just software updates. Most of these complaints are things that I haven't read about elsewhere on the 'net so I thought I would bring them to light and see who else was also having these problems.<br /><ol><li>Speed - Switching between applications is painful, waiting on an application to load before using it is painful, even using some of the applications is painful. And I'm not talking about 3rd party non Apply apps either, this is happening in the contact list. Just opening up the contact list and trying to search for a particular person takes 5-7 seconds before the keyboard responds. Opening up the Maps application has a similar lag and causes me to miss-type quite a lot. Come on Apple, can't you get some speed in your apps?! Hell, the iPhone can run 3D games but it can't let you search a simple contact list without stuttering all over itself? Bah! <br /></li><li>Global search - Speaking of searching stuff, why can't I search for something anywhere on my iPhone? What do I do if I want to find a bookmark or something in my Notes? This is very frustrating and should be an easy fix.</li><li>Free rotation everywhere! - Ok, this one really gets my goat. How come I can rotate my web pages in any direction but email doesn't rotate at all? And YouTube only rotates on one specific side but wont rotate to the other side? I tend to hold my phone with the home button on the left side. This gives me easy access to the volume and mute controls. But whenever I open a YouTube video it's upside down and I'm forced to physically rotate my phone 180°. Photos can be viewed from any angle, but my calendar is stuck in portrait mode. This makes no sense at all! <br /></li><li>To-Do List - What smartphone on the market doesn't have a to-do list? Oh sure, we have a Notes application but its not quite the same and it doesn't even sync with my computer! I know there are hundreds of to-do apps in the iTunes store but they don't sync with my computer either or are not very convenient or pretty. Oh and the iPhone Notes application doesn't rotate either. How nice. :P<br /></li><li>Recent Call info - This one has just hit me pretty recently when I was trying to find out when my wife had called the day before. The only time the iPhone displays the exact time of a call is on the day of that call. After 24 hours or so it switches into fuzzy logic mode and says things like "Yesterday" and "Friday" or "3/3/08" and there is <span style="font-weight: bold;">no</span> way to get an exact time of an old call.<br /></li><li>Safari stability - Web browser stability has gotten better lately but its still way to unstable. I can almost always crash it with a large page (say, a heated Slashdot discussion) or something with a lot of graphics. Sometimes its really bad and crashes after the page is fully loaded and I'm in the middle of reading the content. Like I said, its getting better, but I still experience at least one Safari crash a day and a couple of times in the recent past it's crashed so hard that I had to cold boot my iPhone. That's bad.<br /></li><li><a href="http://en.wikipedia.org/wiki/Bluetooth_profile#Advanced_Audio_Distribution_Profile_.28A2DP.29">A2DP</a> - Ok, is the iPhone an iPod or not? If it is then you would think that they would make listening to music a priority. Why O' why can we not listen to music on our Bluetooth headsets?! For all its hype you would think that the iPhone would make listening to music easy and enjoyable. Not so.<br /></li><li>Bluetooth tethering, Cut & Paste, Flash, and more - these are all the old saws that have been floating around the 'net since the iPhone's launch and I just wanted to give a nod to them. Lately I have seen the need for tethering and how it could have saved me quite a lot of trouble. Lack of Cut & Paste is unacceptable and might be one of the things that drive me to pick up a Palm Pre. And it goes without saying that the missing Flash capability in the browser is a major problem. Fortunately, the word on the street is that the upcoming iPhone 3.0 update will take care of some of these things. We'll see...</li></ol>These things are all pretty minor annoyances but they do keep me from enjoying my iPhone completely and make me think back to my Danger Sidekick with envy. Sure it didn't have YouTube videos and other bits and bobs, but it got the basics right and never slowed me down. Taken together, all these little things are making me look around for other devices that might be better and it looks like Palm might just have what I need in the Pre device. Let's hope Palm doesn't screw it up. :/Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com2tag:blogger.com,1999:blog-5693054744995313095.post-22525781868587259662008-12-09T03:46:00.003-05:002008-12-09T04:01:41.422-05:00External MonitoringI have been trying to do a little external monitoring and statistics for the KangarooBox main website, but haven't had much luck. Basically, my T1 has been giving me problems as of late and I would like to have some numbers to go along with my "feeling" that it is sometimes slow and/or dropping packets. <br /><br />I started off trying to use <a href="http://basicstate.com">BasicState</a> but quickly realized that they are having problems of their own. I get multiple emails per day telling me that they were a target of a DDOS attack several weeks ago and that they are working on fixing it. And the reports they send out are fairly useless with not much data and only a few vague numbers to hint at a possible problem. So I tried to un-register, but that didn't go anywhere. I still received many emails per day. I just now tried to un-register again. We'll see how it goes. :/<br /><br />I'm currently evaluating <a href="http://www.easysitecheck.com">EasySiteCheck</a> but so far I'm not impressed. I haven't gotten much/any SPAM from them but neither have I gotten much value. Their statistics are very minimal and basically just show if your site is online or not. They check every 15 minutes, so they are quick to find out if your site is having problems, but the data to allow you to diagnose the problem is just not there.<br /><br />Maybe I'm going about this the wrong way. I have an internal monitoring system using <a href="http://www.zabbix.org">Zabbix</a> and I'm pretty happy with it, but it can't really monitor the web site as it looks from the outside. And if our T1 is having problems, I'll want some hard numbers to go into the trouble ticket. Does anyone have any suggestions? What would you do?Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-42887579107157946192008-12-05T15:56:00.002-05:002008-12-05T16:09:03.096-05:00More Rails actionI've been working on getting my routes right the past couple of days and I'm happy to report that its working, as far as I can tell. Our new application, <a href="http://github.com/rnhurt/gradesheet/tree/master">GradeSheet</a>, is an online grade management system for the education system. Teachers, students, and eventually parents will be able to log into the system and get an ongoing glimpse into their grades.<br /><br />One of the early problems I'm having is how to arrange the pages in the site. Since teachers & students are both types of users I wanted them to be represented in some similar manner. I finally ended up using a users/+ pattern to reference them. <code>users/students</code> would allow you to work with students while <code>users/teachers</code> would allow you to work with teacher information. This should allow the system to grow into different types of users without much hassle but still allow a unique controller for each user type. That way you can tune a specific user type to do different things (i.e. Students have a <code>Class Of</code> attribute).<br /><br />I have to give thanks to IRC user rolfb on the <a href="irc://#rubyonrails">#rubyonrails</a> channel on <a href="http://freenode.net/">Freenode</a> for all his help. He led me down the road to using route namespaces and made it all possible. He also stuck with me though many of my stupid questions and patiently helped me work through it. Thanks again, rolfb!Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-9317381061838131452008-11-19T06:05:00.002-05:002008-11-19T06:18:29.690-05:00Rails HeadachesOur new project is written in Ruby on Rails. This is the first time that I've been able to get down and dirty with a real Rails project and I have to say that it has its ups and downs. The upside is that I really like the way it 'feels'. There is something about pretty code that is satisfying to an old programmer. When I'm stuck on something that doesn't work for some reason I get that "<span class="blsp-spelling-error" id="SPELLING_ERROR_0">AhHa</span>" moment when it finally clicks. I like that.<br /><br />The downside is that when it fails it fails badly. One example is the pluralization support (requirement ?) in Ruby. Using the name "Campus" for one of my models caused all sorts of problems down the road. It turns out that Ruby doesn't properly pluralize Campus as Campuses and just uses the root word for everything. This obviously isn't the Rails way to do things so I forced it to use the proper pluralization in the Model. While I got it working in Rails proper other things didn't like it. <a href="http://railroad.rubyforge.org/">Railroad</a>, a great little tool to check your models, puked all over the place looking for a table called Campus. Inserting test data using fixtures failed in weird places too. After too much hair pulling and cursing I had to rename it to something more suitable to Rails. <br /><br />I like Rails & Ruby but this something that should be handled more gracefully. A language should not arbitrarily drive the design of a system. Or at least there should be a way to localize plurality settings. :/Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-33608030067134702662008-10-23T07:40:00.004-04:002008-10-23T07:55:23.558-04:00Getting better every dayI know that a lot of people are worried about the recent economic downturn but I think that it will make Open Source an even stronger force in the business world. And it appears as though Jim Whitehurst the President and CEO of <a href="http://www.redhat.com">RedHat</a> agrees. In <a href="http://www.computerworld.com.au/index.php/id;1780559326;fp;16;fpid;1">this ComputerWorld article</a> he says that companies are looking to save money and OSS is in the right place at the right time. If you want more in-depth coverage of the situation, check out the <a href="http://slashdot.org">Slashdot</a> <a href="http://linux.slashdot.org/article.pl?sid=08/10/21/0116221">article</a>.<br /><br />One of the goals of my company is to get great software to those who need it most, and I think business are in real need of a solution that fits their needs and their budgets. Appliance based computing reduces costs even further by reducing complexity in your data center. Now you don't even have to install or maintain the software; just purchase the appliance and plug it in.Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-90051862789340827202008-10-22T11:12:00.002-04:002008-10-22T11:21:53.915-04:00Me @ OLF '08<div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://www.flickr.com/photos/kudzu13/2936706556/" title="photo sharing"><img src="http://farm4.static.flickr.com/3057/2936706556_49743971a8_m.jpg" alt="" style="border: 2px solid rgb(0, 0, 0);" /></a><br /><span style="margin-top: 0px;font-size:0.9em;" ><a href="http://www.flickr.com/photos/kudzu13/2936706556/">OLF2008_096</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kudzu13/">kudzu13</a></span></div>Just a small picture of me winning a book (DoJo) from the guys over at the <a href="http://tllts.org/wiki/index.php">Linux Link Talk Show</a>. We had a great time and I'm really looking forward to next year.<br /><br /><div style="float: right; margin-left: 10px; margin-bottom: 10px;"><a href="http://www.flickr.com/photos/kudzu13/2936710102/" title="photo sharing"><img src="http://farm4.static.flickr.com/3035/2936710102_8fd0ea71e4_m.jpg" alt="" style="border: 2px solid rgb(0, 0, 0);" /></a><br /><span style="margin-top: 0px;font-size:0.9em;" ><a href="http://www.flickr.com/photos/kudzu13/2936710102/">OLF2008_094</a><br />Originally uploaded by <a href="http://www.flickr.com/people/kudzu13/">kudzu13</a></span></div>And this guy is wearing a KangarooBox T-Shirt. I know, your jealous.<br /><br />Rock On!Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0tag:blogger.com,1999:blog-5693054744995313095.post-38569356868125850872008-10-09T11:08:00.003-04:002008-10-09T11:17:22.682-04:00Is there a SO?<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZJN8cMThBvmSN9-XKOuQ_yJyBXAk0dDmmV6Bi2SsQ5Y56UfZ-K0u_xh7l-zuB33tJkwuk_sXUJGZifpS6RzFZi5qx4GfCK-cwNK75ruYsxgGA0fBgtLDS6cVUAbbMz1XmLsgwDDkt1A/s1600-h/IMG_0439.JPG"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZJN8cMThBvmSN9-XKOuQ_yJyBXAk0dDmmV6Bi2SsQ5Y56UfZ-K0u_xh7l-zuB33tJkwuk_sXUJGZifpS6RzFZi5qx4GfCK-cwNK75ruYsxgGA0fBgtLDS6cVUAbbMz1XmLsgwDDkt1A/s200/IMG_0439.JPG" alt="" id="BLOGGER_PHOTO_ID_5255171517507962610" border="0" /></a>OK, here you can see my Wall 'O Cans. I'm just wondering, do you think I drink too much soda for one person? Remember, this wall is about 6 months old and is two rows deep. And I only work on it when I'm at the office (which is most days, but not all day long).<br /><br />Do I have a problem? Should I visit SA (Soda Anonymous)? If you think that is a lot of cans, you should see my <a href="http://www.altoids.com/index.do">Altoids</a> collection. Whew!!<br /><br />EDIT: I really wish that Mountain Dew <a href="http://www.youtube.com/watch?v=uO-J5ImDEi8">Game Fuel</a> was still being made. That soda <a href="http://www.energyfiend.com/2007/04/halo-3-mountain-dew-game-fuel">rocked</a>!Anonymoushttp://www.blogger.com/profile/07196615930668739458noreply@blogger.com0