Hi,

Since the API for accessing heroku application has come out earlier
this week, I have written a rake task that helps me deploy my
applications to heroku.  My situation are as follows:

 1) I already have an external code repository (svn) that I use to
develop my application.
 2) I want to deploy my application to heroku automatically when I
commit to trunk (or create a tag) on the svn tree.
 3) I also have an instance of cruisecontrol.rb setup on another
server.

To achieve (1), I was trying to use git-svnimport, but I just couldn't
figure it out how it works (I just learned about git when the heroku
API came out.)  To that end, I decided to just write something that
will compare the current directory structure against the cloned app.
This method also has the advantage that it works fine with
svn:external (that I like to use for plugins), and would probably work
for projects that use any other repositories, as long as it can be
checked out / updated to the local filesystem.

First, I put the following in lib/tasks/heroku_sync.rake in my project
(the HerokuAppName is hard-coded and needs to be changed):

------
# Synchronizes current filesystem back to heroku
namespace :heroku do
  desc "Synchronizes a repository to a heroku instance"
  task :sync => ['heroku:sync:clone', 'heroku:sync:files',
'heroku:sync:commit', 'heroku:sync:push']

  namespace :sync do
    # The application name. Hard coded for now..
    HerokuAppName = 'my_heroku_app_name'

    # Temporary heroku clone directory
    HerokuDir = 'tmp/heroku'

    # Heroku host
    HerokuHost = 'heroku.com'

    desc "Clones (or pulls) heroku application repository to tmp/
heroku"
    task :clone do
      if FileTest.exist? HerokuDir
        Dir.chdir HerokuDir do
          raise "Could not pull #{HerokuDir}" unless system "git
pull ."
        end
      else
        raise "Could not clone #{HerokuAppName}" unless
          system "git clone [EMAIL PROTECTED]:#{HerokuAppName}.git
#{HerokuDir}"
      end
    end

    desc "Synchronizes difference between the current filesystem and
the heroku clone"
    task :files do
      require 'find'
      require 'fileutils'
      require 'ftools'

      def for_difference_between src_dir, dest_dir
        Dir.chdir(dest_dir) do
          Find.find(src_dir) do |src_path|
            path = src_path.sub(src_dir, '')
            next if path.empty?

            fdir, base = File.split(path)
            if base[0] == ?. or
              base =~ /^(?:CVS|bak)/i or
              fdir =~ /^(?:log|db|tmp)/i
              Find.prune
              next
            end

            case
            when !FileTest.exist?(path)
              yield :added, path
            when File.stat(src_path).ctime > File.stat(dest_dir + '/'
+ path).ctime
              yield :modified, path unless FileTest.directory? path #
Don't fire dir modification events
            end
          end
        end
      end

      from_dir = File.expand_path('.') + "/"
      to_dir = File.expand_path(HerokuDir) + "/"

      # Add / copy files and dirs that exist in source but not in
target
      for_difference_between from_dir, to_dir do |event, path|
        from_path = from_dir + '/' + path
        to_path   = to_dir + '/' + path

        if FileTest.directory? from_path
          File.makedirs to_path
        else
          File.copy from_path, to_path
        end

        STDERR.puts "Adding #{path}"
        Dir.chdir(to_dir) do
          case event
          when :added
            system "git add #{path}"
          when :modified
            system "git add #{path}"  # Seems like git wants 'add'
issued even for modifications
          end
        end
      end

      # Remove files / dirs that exist in taget but not in source
      for_difference_between to_dir, from_dir do |event, path|
        Dir.chdir(to_dir) do
          case event
          when :added
            if FileTest.directory?(to_dir + '/' + path)
              system "git rm -r #{path}"
            else
              system "git rm #{path}"
            end
          end
        end
      end

    end

    desc "Commits the changes"
    task :commit do
      Dir.chdir HerokuDir do
        raise "Could not commit #{HerokuDir}" unless
          system "git commit -m 'heroku:sync:files at #{Time.now}'"
      end
    end

    desc "Pushes the changes back to heroku repository"
    task :push do
      Dir.chdir HerokuDir do
        raise "Could not push #{HerokuAppName}" unless
          system "git push"
      end
    end
  end
end
------

... Once this file is in place, 'rake heroku:sync' will (a) pull the
project git on tmp/heroku, (b) compare the current project structure
against the one just pulled, (c) issues 'git add' / 'git remove' then
commit, and (d) pushes the changes back to heroku.  (Note: for all
this to work, you would need to have the project checked out under the
same user at least once using 'heroku clone [myapp]', since it would
need to upload ssh key and save username.)

Although the task above can be run manually, I wanted cruisecontrol.rb
to run it for me.  So I added the following to my Rakefile:

------
desc "Cruise task with automatic heroku deployment"
task :deploy_heroku => ['db:test:purge', 'db:migrate', 'test',
'heroku:sync']
------

and added the following to my cruisecontrol project setting:

------
"project.rake_task = 'deploy_heroku'"
------

Now, when I commit to trunk (or tag or whereever cruisecontrol.rb is
watching,) it gets automatically deployed to heroku (only if it passes
'test' task.)  I'm sure it can be improved / done in simpler ways, but
hope it helps someone...

Tomoki

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Heroku" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/heroku?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to