Thursday, December 27, 2012

Setting up Ubuntu with Rails, Git, Heroku, Capybara and more

I've been following Michael Hartl's excellent Ruby on Rails tutorial. One nice thing about is it that it not only explains Rails, but guides the reader through setting up an entire development ecosystem with GitHub for source control, Heroku for deployment, RSpec and Capybara for testing, and Bootstrap for stylesheets.

I'm using an Amazon Web Services Ubuntu instance. Hartl provides instructions for setting up all the required software on a variety of platforms, including Linux. For the most part, his instructions worked for me, but I found that a few tweaks were needed. This article documents how I set up my AWS Ubuntu box.

Prerequisites:

  • An AWS account with a keypair
  • A GitHub account
  • A Heroku account


Launch the AWS EC2 instance

  • Log in to your AWS account.
  • On the EC2 Dashboard, click Launch Instance
  • Click Classic Wizard. Step through the wizard, accepting the default, except:
    • Choose the AMI Ubuntu Server 12.04.1 LTS (64-bit).
    • I set the Instance Type to T1 Micro. You can use any Instance Type you wish. T1 Micro is a bit underpowered, but qualifies for AWS's free tier.
    • Select your existing keypair.
    • Set the Security Group to quick-start-1. Or use any Security Group you wish. Configure the Security Group to provide inbound access on TCP 22, 80 and 3000 for any IP addresses that will access the server.
  • After you complete the wizard, you may want to give your instance a name. I called mine Ubuntu Ruby 3.
  • Connect to your instance for the first time:
    • Make sure your browser supports Java.
    • Right-click the instance in the EC2 Dashboard and select Connect.
    • Change the user name to ubuntu.
    • For the private key path, browse to your keypair file (e.g. mine is located at C:\Users\ssaporta\Desktop\stevekeypair.pem).
    • Check the Save key location box.
    • Click Launch SSH Client. Respond affirmatively to any security dialogs that may appear, so that the Java SSH client can run.
    • When prompted Do you want to add this host? click Yes.

Install Git, RVM, Ruby and Rails

Section 1.2.2 of the tutorial  provides instructions for Git, RVM, Ruby and Rails installation, but I had some trouble with them on Ubuntu. Based on this Sudobits Blog entry, I found the steps below worked better:
  • Launch the SSH client to connect to your instance.
  • Enter sudo apt-get update.
  • Enter sudo apt-get install git.
  • Enter curl -L get.rvm.io | bash -s stable.
  • Enter source ~/.rvm/scripts/rvm.
  • Enter sudo /usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config.
  • Enter sudo apt-add-repository ppa:chris-lea/node.js.
  • Enter sudo apt-get update.
  • Enter sudo apt-get install nodejs.
  • Enter rvm install 1.9.3. On a T1 Micro instance, this could take half an hour or so.
  • Enter rvm use 1.9.3 --default.
  • Enter gem install rails. This, too, could take a while on a T1 Micro instance.

Create and publish your first Rails app

This section walks through the creation of a simple Rails app, but my real point in including it is to show the steps for setting up pushes to GitHub and Heroku.
  • Launch the SSH client to connect to your instance.
  • Create the file ~/.gemrc with these contents:
install: --no-rdoc --no-ri
update: --no-rdoc --no-ri
  • Create a folder and create a Rails project in it:
    • Enter mkdir rails_projects.
    • Enter cd rails_projects.
    • Enter rails new first_app.
    • Enter cd rails_projects/first_app.
  • Set up Git and GitHub, and perform your first commit:
    • Enter git config --global user.name "Your Name". Replace Your Name with your actual name.
    • Enter git config --global user.email you@example.com. replace you@example.com with your actual email address.
    • Enter git init.
    • If desired, edit .gitignore as Hartl suggests
    • Enter git add . (yes, that's a dot at the end).
    • Enter git commit -m “Initial commit”.
    • Enter cd ~/.ssh.
    • Enter ssh-keygen -t rsa -C "you@example.com". Replace you@example.com with your actual email address. Press Enter three times to use the default filename and a blank passphrase.
    • Enter cat ~/.ssh/id_rsa.pub. Select the output and press Ctrl+Ins to copy it to the clipboard.
    • In a web browser on your own computer, log in to your GitHub account and create a new repository:
      • Set the repository name to first_app.
      • Set the description to The first app for the Ruby on Rails tutorial.
      • Select Public.
      • Do not check the box to initialize this repository with a README.
    • In GitHub, click the Account Settings icon.
    • Click SSH Keys.
    • Click Add SSH Key. Enter a title (e.g. ubuntu). Paste the key from the clipboard. Enter your password when prompted.
    • Return to your SSH client and enter cd ~/rails_projects/first_app.
    • Enter git remote add origin git@github.com:username/first_app.git. Replace username with your actual GitHub username.
    • Enter git push -u origin master. When prompted, enter yes.
  • Deploy your Rails app to Heroku:
    • Enter wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh.
    • Enter heroku login. Enter your Heroku username and password when prompted.
    • Enter heroku keys:add ~/.ssh/id_rsa.pub.
    • Enter git push heroku master.
    • Note the URL displayed in the output. You can view your app on Heroku at this URL.

At this point, you should be all set to develop Rails apps, test them with RSpec and Capybara, push them to GitHub, and deploy them to Heroku.

Friday, December 7, 2012

An excellent tutorial for node.js noobs

I just completed Manuel Kiessling's Node Beginner Book. Great tutorial! In two hours, I had a working node.js app and a decent appreciation of what node.js is all about. Furthermore, I got everything working on Windows 7 without any trouble. To install node.js on Windows 7, I just went to http://nodejs.org/ and clicked the INSTALL button.

Wednesday, August 29, 2012

Using the AWS .NET SDK to look up an EC2 instance's IP address

I found a lot of documentation about the AWS API, and a lot of documentation about the AWS .NET SDK, but nothing that put together all the pieces required to actually write a working C# program that calls the AWS .NET SDK to do real work. In particular, the syntax to filter a list of instances, and to access the properties of an instance, wasn't easy to guess. I have written such a program. It looks up an EC2 instance based on its public IP address, and outputs the instance's private IP address. You could easily modify this to do the lookup based on other filter criteria, and to output additional properties of the instance. Of course you might want to add some nicer error handling and user interface code. Start by creating a C# Windows Console App. I happen to have used Visual Studio 2010 Professional. Using Nuget, search online and add the package "AWS SDK for .NET". I also found it necessary to explicitly add a reference to System.Configuration. Then you'll just need to create two files, app.config and program.js. App.config should look like this, substituting your own AWS access key and AWS secret key: Program.cs should look like this: /* Takes the public IP address of an AWS EC2 instance as an argument. Outputs that instance's private IP address. Assumes there is exactly one instance with the given public IP address. Be sure to specify your own AWS access key and secret key in app.config. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; using System.Collections.Specialized; using Amazon; using Amazon.EC2; using Amazon.EC2.Model; namespace AwsGetInstanceIP { class Program { static void Main(string[] args) { if (args.Length != 1) { Console.WriteLine("Must specify public IP address"); return; } NameValueCollection appConfig = ConfigurationManager.AppSettings; AmazonEC2 ec2 = AWSClientFactory.CreateAmazonEC2Client( appConfig["AWSAccessKey"], appConfig["AWSSecretKey"], RegionEndpoint.USEast1 // change if your instances are in an AWS region other than us-east ); DescribeInstancesRequest request = new DescribeInstancesRequest(); Filter filter = new Filter(); filter.WithName("ip-address"); filter.WithValue(args[0]); request.Filter.Add(filter); DescribeInstancesResponse ec2Response = ec2.DescribeInstances(request); int numReservations = ec2Response.DescribeInstancesResult.Reservation.Count; if (numReservations != 1) { Console.WriteLine("Expecting exactly 1 reservation, but found " + numReservations); return; } int numInstances = ec2Response.DescribeInstancesResult.Reservation[0].RunningInstance.Count; if (numInstances != 1) { Console.WriteLine("Expecting exactly 1 instance, but found " + numInstances); return; } string privateIpAddress = ec2Response.DescribeInstancesResult.Reservation[0].RunningInstance[0].PrivateIpAddress; Console.WriteLine(privateIpAddress); } } }

Wednesday, July 25, 2012

Old version of Ajax Control Toolkit causes "strict mode" error

My .NET website was working fine in IE, Chrome and Firefox. Then one page stopped working in Chrome and Firefox, while continuing to be okay in IE. Since I didn't change the website, I assume upgrades to Chrome and Firefox introduced the problem. The problem was on a page that uses Ajax Control Toolkit to create an UpdatePanel to update just a portion of the page. That portion of the page was no longer being displayed. In other words, a big chunk of the user interface was missing. In Chrome's JavaScript console, I found an error message, "Illegal access to a strict mode caller function." In Firefox's error console, I found a similar error message, "access to strict mode caller function is censored." Some Google searching indicated this error has to do with ECMAScript 5's strict mode. However, nowhere in my Visual Studio solution was there any explicit reference to "use strict." So I figured the problem must be in Microsoft's generated code or a third-party vendor's code. I could tell from the Chrome JavaScript console that the error occurred in line 5 of ScriptResource.axd. This line begins with Type._registerScript("MicrosoftAjaxWebForms.js"... This was a good hint that the problem might be in the Ajax Control Toolkit. In my Visual Studio 2010 web application project, I upgraded the Ajax Control Toolkit from version 2.x to the latest version, 4.1.60623.0. I did so by expanding the projects References folder, deleted AjaxControlToolkit, then using nuget to add the Ajax Control Toolkit to the solution and apply it to the affected project. This fixed the problem.

Friday, June 29, 2012

Ruby error "invalid multibyte character (US-ASCII)"

If you try to include non-ASCII characters in the source code of a Ruby program, you may get the error "invalid multibyte character (US-ASCII)". This one-line program illustrates the problem, using the right angle quote character: foo = "»" If you Google the error message, you'll find a lot of advice about placing "# encoding: UTF-8" or "# coding UTF-8" or "$KCODE = 'u'" at the top of the file. None of these worked for me. However, I found a simple fix. My environment is Ruby 1.9.3p194 on Windows. I opened my .rb file in Notepad, selected Save As, chose "UTF-8" from the "Encoding" dropdown, and clicked "Save". This -- with no other changes made -- solved the problem.

Monday, June 25, 2012

Enumerating the rows and columns for an arbitrary SQL query using Dapper ORM

The Dapper object-relational mapper makes it easy to execute SQL queries and manipulate the results in a .NET program. I thought it would be nice to be able to execute an arbitrary query, without knowing in advance the number, names and data types of the resulting columns, and output the results. The C# program below does exactly that.

Before using this in the real world, consider:

  • Error handling. What happens if the connection string is wrong? If the query times out? If the query returns no results? Etc.
  • Security. Do you want everyone to be able to execute any query against your database?!
  • Performance: What if someone constructs a query that takes an hour to execute?
Here's the code, which assumes you're building a C# Console Application and have added Dapper's SqlMapper.cs to the project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dapper;
using System.Data.SqlClient;
using System.Reflection;
using System.Collections;

static void Main(string[] args)
{
    string connectionString = "YOUR CONNECTION STRING GOES HERE";
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        var rows = (IEnumerable<IDictionary<string, object>>)conn.Query("ANY SQL QUERY YOU LIKE"); // e.g. select top 10 * from FooBar
        foreach (var row in rows)
        {
            foreach (var column in row)
            {
                Console.WriteLine(column.Key + " = " + column.Value);
            }
        }
    }
}

Tuesday, May 22, 2012

Increasing an AWS instance's disk space by adding an EBS volume

I wanted to provide more free disk space for my AWS instance. I had previously discovered that restoring a snapshot to a larger EBS volume doesn't easily accomplish this, so instead I added a second EBS volume to the instance. Here's how to do so using the AWS Management Console:

Click Volumes.

Click the Create Volume button.

Type in the desired size (I entered 30 GiB). Select the Availability Zone, making sure it's the same one used by your instance. From the Snapshot dropdown, select --- No Snapshot ---.

Right-click the newly created volume and select Attach Volume. Select your instance. Accept the default value for Device. (If the state doesn't change immediately from attaching to attached, try refreshing the page.)

At first, when I used Windows Explorer to view the disks in a Remote Desktop session on my instance, I didn't see the new disk. Logging off and back on didn't help. Stopping and restarting the instance did the trick. I'm not actually sure if that's what helped, or if I just needed to wait a few minutes, or what. But in the end, it worked. In addition to the C: drive, I now had an empty D: drive with a capacity of 29.9 GB.

Restoring an AWS instance from a snapshot

I had created an Amazon Web Services instance. As is the default when creating an instance, it used EBS as the root device.

A few days ago, when I had it set up just the way I liked it, I created a snapshot. Then I did some experimental work, didn't like the results, and wanted to restore the snapshot to revert. Here's how to do that using the AWS Management Console:




  1. In the AWS Control Panel, click Instances, then click on your instance. Instance details will be displayed at the bottom of the page. Note the instance's name (Steve-Jenkins in my example; yours will be different), instance ID (i-4b33962d), zone (us-east-1c), and root device (sda1).
  2. If the instance is running, stop it.
  3. Click Volumes. Look at the Attachment Information column to locate the volume currently attached to your instance. Right-click this volume and select Detach Volume. (The volume's state might not change from in-use to available right away. If it doesn't, try refreshing the page.)
  4. Click Snapshots. Right-click your snapshot and select Create Volume from Snapshot. Set the size (30 GiB in my example) and Availability Zone. Make sure the Availability Zone matches the one you noted in step 1.
  5. Return to Volumes. Right-click the newly created volume and select Attach Volume. Select your instance. For Device, type in  the root device you noted in step 1, but be sure to prepend /dev/. In my example, I entered /dev/sda1.
  6. To avoid charges for the old volume that's no longer attached to your instance, right-click it and select Delete Volume.
  7. Click Instances, right-click your instance, and select Start Instance. If you created and attached the volume correctly, the instance should start without error.
Tip: If you had an Elastic IP attached to your image, don't forget to reattach it now. Click Elastic IPs, right-click the IP address, select Associate, and select your instance.

Another tip: I wanted more disk space, so I tried making the new volume larger -- 60 GiB rather than 30 GiB -- in step 4. This didn't work. I ended up with a 60 GiB volume, but only 30 GiB was in a usable boot partition. The rest was in an unallocated partition, and I couldn't find a convenient way to merge the two partitions without losing data. A better alternative might be to create a second volume and attach it to the same instance.

Wednesday, May 9, 2012

Using Seleniun WebDriver with Windows and .NET

Wanting to write a C# program to do some automated functional testing in multiple browsers, I downloaded Selenium WebDriver version 2.21.0.

I also downloaded the Selenium Client Drivers for C#. At the time I first downloaded these, the version was 2.16.0.

I proceeded to create a Selenium hello-world program by following the C# example provided here. With all the comments and fluff stripped out, my program looked something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;

namespace WebDriver1
{
    class Program
    {
        static void Main(string[] args)
        {
            IWebDriver driver = new FirefoxDriver();
            driver.Navigate().GoToUrl("
http://www.google.com/");
            IWebElement query = driver.FindElement(By.Name("q"));
            query.SendKeys("Cheese");
            query.Submit();
            WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
            wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
            System.Console.WriteLine("Page title is: " + driver.Title);
            driver.Quit();
        }
    }
}

One note right off the bat: The example code at seleniumhq.com doesn't include using OpenQA.Selenium.Support.UI. This is needed, or else WebDriverWait can't be resolved.

When I tried to run this program, it threw an exception: OpenQA.Selenium.WebDriverException : Failed to start up socket within 45000. I found some stuff about this online, but none of it helped. What solved the problem was downloading the latest version, 2.21.0, of the Selenium Client Drivers.

With those two problems solved, the program compiled and ran, launching Firefox, doing a Google search for "cheese," and outputting the page title. Pretty cool!

Next, I wanted to do the same thing using IE.

The first stumbling block was when I added this line to my code: driver = new InternetExplorerDriver();

This class wasn't found. Easily remedied by adding this as well: using OpenQA.Selenium.IE;

My whole program now looked like this, executing a loop twice, once for each browser:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Support.UI;

namespace WebDriver1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 2; i++)
            {
                string browserName = "";
                IWebDriver driver = null;
                switch (i)
                {
                    case 0:
                        browserName = "FireFox";
                        driver = new FirefoxDriver();
                        break;
                    case 1:
                        browserName = "IE";
                        driver = new InternetExplorerDriver();
                        break;
                }
                driver.Navigate().GoToUrl("
http://www.google.com/");
                IWebElement query = driver.FindElement(By.Name("q"));
                query.SendKeys("Cheese");
                query.Submit();
                WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
                wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
                System.Console.WriteLine(browserName + ": Page title is: " + driver.Title);
                driver.Quit();
            }
        }
    }
}

This worked for Firefox but threw an exception when it opened Internet Explorer.
System.InvalidOperationException: Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (NoSuchDriver)

Surprisingly, this meant exactly what it said, and was corrected by opening IE, selecting Tools | Internet Options | Security, clicking on all four security zones (Internet, Local Intranet, Trusted Sites and Restricted), ensuring the Enable Protected Mode checkbox was checked for each zone, and restarting IE. (Unchecking the checkbox would work, too. The point is that the setting for all four zones must be the same.) A huge tip of the hat to Tom Dupont for that.

With IE's Protected Mode settings properly configured, the above program ran successfully, automating both browsers.

Friday, April 6, 2012

Database trigger can cause problems with @@IDENTITY

My .NET web application with a SQL Server back end was working fine, until...

I tested a feature that added a new record to one of the database tables, and I got an exception saying that a primary key constraint had been violated.

Using SQL Server Profiler, I was able to isolate the stored procedure call that violated the constraint.

Examining the SQL code in that sproc, I saw something similar to this:
DECLARE @newID INT
INSERT INTO Table1 (Foo, Goo) VALUES ('abc', 'def')
SET @newID = @@IDENTITY
INSERT INTO Table2 (Bar, Car) VALUES (@newID, 'ghi')

The problem was that someone had recently added a trigger on Table1. The trigger fired upon inserting a row into Table1, and executed some code that did an insert into some other table, changing the value of @@IDENTITY. The new value of @@IDENTITY happened to be one that was already in use as the primary key of a row in Table2, violating the constraint.

The fix was simple: use SCOPE_IDENTITY() in place of @@IDENTITY. Thanks to this article for that tip.