Migrating APEX via ANT

Recently I was tasked with migrating approximately 550,000 lines of APEX code from a Salesforce Sandbox to a Production Org.  Not a small task.  Development on the code base had started about 18 months ago and was full of fun business logic instituted via APEX and regular Validation Rules.  Not necessarily a “fun” package to deploy via the common UI tools.

This push consisted of hundreds of classes, pages, triggers, static resources, page layouts, profiles, tabs, and custom links.  Absolutely doable for Eclipse and Change Sets, but because we didn’t have two weeks to build the Change Set and didn’t want to continually re-select the components via Eclipse, I set out to see how native ANT would fit the need.

Typically, the code pushes I do consist of deploying via Eclipse or more recently taking the time to build a Change Set, but for reasons I won’t get into deep detail here, I also needed to push things in separate distinct “batches”.

Situation:  There were seven distinct “batches” of changes to migrate to Prod, each consisting of 10-500 “things”.  I wanted to be able to just specify what “things” I wanted in each step and execute one big batch file to shove it all in.  Here is what I did:

1.  Install ANT on your machine (Salesforce link)

2.  Create a fresh directory in your ANT install directory (I install things to C:\Projects…so this was C:\Projects\ANTSalesforce\<Project Name> Deploy)

3.  Expand your “build.properties” file to include both your SBX and PROD credentials:

 # build.properties
 #
 # SBX
 sf.usernameSBX = <your SBX username>
 sf.passwordSBX = <your SBX password>
 sf.serverurlSBX = https://test.salesforce.com
 #
 # PROD
 sf.usernamePROD = <your PROD username>
 sf.passwordPROD = <your PROD password>
 sf.serverurlPROD = https://www.salesforce.com
 #
 # (you could expand this file for other environments as well...)

4.  Once you have that done, I modified my build.xml to specify the packages that will be processed in the overall batch:

<project name="ANT Deployment Batcher" default="test" basedir="." xmlns:sf="antlib:com.salesforce">
 <property file="build.properties" />
 <property environment="env" />
<target name="retrieveStep1">
 <mkdir dir="Step1">
 <sf:retrieve
 username="${sf.usernameSBX}"
 password="${sf.passwordSBX}"
 serverurl="${sf.serverurlSBX}"
 retrieveTarget="Step1"
 unpackaged="unpackaged/1fromSandboxBaseObjects.xml"
 pollWaitMillis="10000" maxPoll="100" />
</target>
<target name="retrieveStep2">
 <mkdir dir="Step2">
 <sf:retrieve
 username="${sf.usernameSBX}"
 password="${sf.passwordSBX}"
 serverurl="${sf.serverurlSBX}"
 retrieveTarget="Step2"
 unpackaged="unpackaged/2fromSandboxClassesAndPages.xml"
 pollWaitMillis="10000" maxPoll="100" />
</target>
(others here, but truncated...you get the point)
<target name="deployStep1">
 <sf:deploy
 username="${sf.usernamePROD}"
 password="${sf.passwordPROD}"
 serverurl="${sf.serverurlPROD}"
 deployroot="Step1"
 checkonly="false"
 pollWaitMillis="10000" maxPoll="100" />
</target>
<target name="deployStep2">
 <sf:deploy
 username="${sf.usernamePROD}"
 password="${sf.passwordPROD}"
 serverurl="${sf.serverurlPROD}"
 deployroot="Step2"
 checkonly="false"
 pollWaitMillis="10000" maxPoll="100" />
</target>
(others here, but truncated...you get the point) 
</project>

5.  Define your package files (again, samples…rinse and repeat for additional files / steps)
(1fromSandboxBaseObjects.xml)

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
	<members>Custom_Object_1__c</members>
	<members>Custom_Object_2__c</members>
	<members>Activity</members>
	<members>Task</members>
	<members>Event</members>
	<name>CustomObject</name>
    </types>
    <version>22.0</version>
</Package>

(2fromSandboxClassesAndPages.xml)

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
	<members>*</members>
	<name>ApexClass</name>
    </types>
    <types>
	<members>*</members>
	<name>ApexPage</name>
    </types>
    <types>
        <members>*</members>
        <name>ApexPage</name>
    </types>
    <types>
	<members>*</members>
	<name>CustomPageWebLink</name>
    </types>
   <version>22.0</version>
</Package>

6.  Build your batch file

@ECHO OFF
ECHO THIS IS THE BATCH DEPLOYMENT SCRIPT.  PRESS ANY BUTTON TO START!
PAUSE
ECHO DELETING ANY PREEXISTING MIGRATION DIRECTORIES
RMDIR /S /Q Step1
RMDIR /S /Q Step2
DEL *.txt /Y
ECHO RETRIEVING CODE FROM SANDBOX
CALL ANT retrieveStep1 >Step1_Retrieve_Log.txt
CALL ANT retrieveStep2 >Step2_Retrieve_Log.txt

ECHO DEPLOYING CODE TO PRODUCTION
CALL ANT deployStep1 >Step1_Deploy_Log.txt
ECHO SUCCESSFUL BUILD?  PRESS ANY KEY TO CONTINUE TO NEXT STEP
PAUSE
CALL ANT deployStep2 >Step2_Deploy_Log.txt
ECHO SUCCESSFUL BUILD?  PRESS ANY KEY TO CONTINUE TO NEXT STEP
PAUSE

That’s really all there is to it.  Took me a little to work out the kinks, but it works slick!  The biggest hurdle I had to deal with was in the batch file…to Windows, ANT works like another batch file – and natively, batch files can call other batch files, but they don’t wait for them to finish.  The DOS “CALL” command calls the ANT “batch” script but WAITS until it’s complete before continuing.  Finally, I directed the output to some local TXT files so I could go back and deal with any errors that came out of an unsuccessful deployment.

Hundreds of miles and working a lot!

Wow it’s been a long time since I wrote on here. I’ve put down about 550 miles on the bike since the last post, including my first century ride and my first ride with Team Cargill on the MS150. Both INCREDIBLE events and I’m looking forward to doing both again next year.

Century Ride – the Leinenkugel’s Century Ride (102 miles, about 7 hours).  Incredible scenery, but holy heck the far end of the route had a 4-5 mile mostly uphill grade.  Insane, but so incredibly rewarding crossing that finish line.  The free Leinenkugel’s beer and brats were a nice touch.  =)

MS150 – Duluth, MN to White Bear Lake, MN over two days (150 miles, combined approx 8 hours of wheels spinning time).  This is the first event that I’ve truly experienced the benefit of pack/group riding.  There was a point on the first day where a group of us were cruising at a fairly sustained 26-28mph for about 6 miles, and a VERY large pack of Target, UnitedHealthGroup, Cargill, and solo riders in a draft line of about 50-60 doing 23-25mph for about 12 miles.  One of the ladies on the Target team had a radio pumping out some music a few riders up from us in that line, so that made the time fly by even faster.

Most of my time since the last post has been settling into my new role at Demand Chain Systems.  I came on in January as a “Senior CRM Technologist”, working on every facet of the business from sales support / light PM to hardcore APEX coding.  Picked up my Salesforce Admin and Developer certifications and haven’t looked back since.  Salesforce is such an incredible platform that allows users and developers to focus on quickly delivering real-world applicable solutions rather than monkey around with framework and building webpages from scratch.  With too many awesome features to list, a Java-based programming language on top of it (APEX / Visualforce) practically allows you to build anything you want.  More on that in later posts.

Also in other biking news, I logged my 2,000th road mile since getting back on the bike in 2010.  I was leading a ride for the Hiawatha Bike Club down south of my house and I yelled out “WHOOOO!” as my bike compy clicked that last 1/10th of a mile.  Awesome!  Only 1,189 miles left to hit my 2,000 mile goal for the year.  Hopefully the Minnesota weather will start cooperating with me.

Warrior Dash, Here I Come!

Running a 5K is one thing, but running a 5K through a wooded area overcoming obstacles such as:

  • Sliding down a muddy hill
  • Crawling through tunnels
  • Climbing over a wall
  • Rappelling down a ravine
  • Climbing up large hay bales
  • Crawling over old junked cars
  • Running through sand piles
  • Running up a ski hill
  • Climbing up and over a cargo net
  • Jumping over a wall of FIRE
  • Army-crawling under barbed wire

Am I nuts??!  Yeah, pretty much, but I get a helmet with horns as a trophy for completing it.  That’s worth the price of admission.

July 23rd, 2011 – 10am, shoes and whatever crazy outfit I feel like wearing – good to go.

Now – only to get myself to be able to RUN a 5k. I can bike 75 miles in one shot without much issue, but running is a totally different story.  Ooorah.