Skip to content

Instantly share code, notes, and snippets.

@afawcett
Last active December 29, 2022 17:11
Show Gist options
  • Save afawcett/5843110 to your computer and use it in GitHub Desktop.
Save afawcett/5843110 to your computer and use it in GitHub Desktop.
Defines some macros around sf:deploy to install and uninstall packages.
<!-- TODO: Review Ant v1.8 local properties -->
<project xmlns:sf="antlib:com.salesforce">
<!-- Download from Salesforce Tools page under Setup -->
<typedef
uri="antlib:com.salesforce"
resource="com/salesforce/antlib.xml"
classpath="${basedir}/lib/ant-salesforce.jar"/>
<!-- Download from http://sourceforge.net/projects/ant-contrib/files/ant-contrib/1.0b3/ -->
<taskdef
resource="net/sf/antcontrib/antlib.xml"
classpath="${basedir}/lib/ant-contrib-1.0b3.jar"
/>
<!-- Download from https://code.google.com/p/missing-link/ -->
<taskdef
name="http"
classname="org.missinglink.ant.task.http.HttpClientTask"
classpath="${basedir}/lib/ml-ant-http-1.1.3.jar"/>
<!-- Download from http://www.oopsconsultancy.com/software/xmltask/ -->
<taskdef
name="xmltask"
classname="com.oopsconsultancy.xmltask.ant.XmlTask"
classpath="${basedir}/lib/xmltask.jar"/>
<macrodef name="installPackage" description="Installs the given managed package">
<attribute name="namespace" description="Namespace of managed package to install."/>
<attribute name="version" description="Version of managed package to install."/>
<attribute name="packagePassword" description="Password used to install the pacakge. Optional." default=""/>
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<sequential>
<!-- Generate optional <password> element? -->
<if><equals arg1="@{packagePassword}" arg2=""/>
<then><property name="passwordElement" value=""/></then>
<else><property name="passwordElement" value="&lt;password&gt;@{packagePassword}&lt;/password&gt;"/></else>
</if>
<!-- Generate working folder and metadata files representing the package to install -->
<delete dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy/installedPackages"/>
<echo file="${basedir}/installdeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>@{namespace}</members><name>InstalledPackage</name></types><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/installedPackages/@{namespace}.installedPackage"><![CDATA[<InstalledPackage xmlns="http://soap.sforce.com/2006/04/metadata"><versionNumber>@{version}</versionNumber>${passwordElement}</InstalledPackage>]]></echo>
<sf:deploy deployRoot="${basedir}/installdeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<macrodef name="uninstallPackage" description="Uninstalls the given managed package">
<attribute name="namespace" description="Namespace of managed package to install."/>
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<sequential>
<!-- Generate working folder and metadata files representing the package to uninstall -->
<delete dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy/installedPackages"/>
<echo file="${basedir}/installdeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/destructiveChanges.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>@{namespace}</members><name>InstalledPackage</name></types><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/installedPackages/@{namespace}.installedPackage"><![CDATA[<InstalledPackage xmlns="http://soap.sforce.com/2006/04/metadata"><versionNumber>@{version}</versionNumber></InstalledPackage>]]></echo>
<sf:deploy deployRoot="${basedir}/installdeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<!-- Deploys the given file as a static resource via the sf:deploy Ant task (Metadata API) -->
<macrodef name="staticResource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="developername" description="Developer name for the Static Resource."/>
<attribute name="file" description="Source file for the static resource."/>
<attribute name="contenttype" description="Content type of the Static Resource."/>
<sequential>
<!-- Generate working folder and metadata files representing the package to uninstall -->
<delete dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy/staticresources"/>
<echo file="${basedir}/staticresourcedeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>*</members><name>StaticResource</name></types><version>29.0</version></Package>]]></echo>
<copy file="@{file}" tofile="${basedir}/staticresourcedeploy/staticresources/@{developername}.resource"/>
<echo file="${basedir}/staticresourcedeploy/staticresources/@{developername}.resource-meta.xml"><![CDATA[<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"><cacheControl>Private</cacheControl><contentType>@{contentType}</contentType></StaticResource>]]></echo>
<sf:deploy deployRoot="${basedir}/staticresourcedeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce Tooling REST API ExecuteAnnoynmous resource -->
<macrodef name="executeApex" description="Provides access to the Salesforce Tooling REST API ExecuteAnnoynmous resource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="resultprefix" description="Property name prefix used for properties containing response data" default="executeAnonymousResponse"/>
<attribute name="failonerror" description="If the execute fails then fail the Ant script" default="true"/>
<text name="apexcode"/>
<sequential>
<!-- Login -->
<login username="@{username}" password="@{password}" serverurl="serverUrl" sessionId="sessionId"/>
<!-- Execute Apex via Tooling API /executeAnonymous resource -->
<http url="https://na11.salesforce.com/services/data/v29.0/tooling/executeAnonymous" method="GET" entityProperty="executeAnonymousResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<query>
<parameter name="anonymousBody" value="@{apexcode}"/>
</query>
</http>
<!-- Parse JSON response and set properites -->
<script language="javascript">
var response = eval('('+project.getProperty('executeAnonymousResponse')+')');
for(field in response)
project.setProperty('@{resultprefix}.' + field, response[field]);
</script>
<!-- Fail on error?-->
<if>
<and>
<equals arg1="@{failonerror}" arg2="true"/>
<equals arg1="${@{resultprefix}.success}" arg2="false"/>
</and>
<then>
<if>
<equals arg1="${@{resultprefix}.compiled}" arg2="false"/>
<then>
<fail message="${@{resultprefix}.line}:${@{resultprefix}.column} ${@{resultprefix}.compileProblem}"/>
</then>
<else>
<fail message="${@{resultprefix}.exceptionMessage} ${@{resultprefix}.exceptionStackTrace}"/>
</else>
</if>
</then>
</if>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce Tooling REST API StaticResource resource -->
<macrodef name="staticResource.toolingapi" description="Provides access to the Salesforce Tooling REST API StaticResource resource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="developername" description="Developer name for the Static Resource."/>
<attribute name="body" description="Base64 encoded data for the Static Resource."/>
<attribute name="contenttype" description="Content type of the Static Resource."/>
<sequential>
<!-- Login -->
<login username="@{username}" password="@{password}" serverurl="serverUrl" sessionId="sessionId"/>
<!-- Create the StaticResource (later versions of this macro can support other operations) -->
<http url="https://na11.salesforce.com/services/data/v29.0/tooling/sobjects/StaticResource/" printresponse="true" printrequest="true" expected="201" method="POST" entityProperty="executeAnonymousResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Content-Type" value="application/json"/>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<entity>
{
"Name": "@{developerName}",
"Body": "@{body}",
"ContentType" : "@{contentType}"
}
</entity>
</http>
</sequential>
</macrodef>
<!-- Login into Salesforce and return the session Id and serverUrl -->
<macrodef name="login">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="serverurl" description="Server Url property."/>
<attribute name="sessionId" description="Session Id property."/>
<sequential>
<!-- Obtain Session Id via Login SOAP service -->
<http url="https://login.salesforce.com/services/Soap/c/29.0" method="POST" failonunexpected="false" entityProperty="loginResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Content-Type" value="text/xml"/>
<header name="SOAPAction" value="login"/>
</headers>
<entity>
<![CDATA[
<env:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
<env:Body>
<sf:login xmlns:sf='urn:enterprise.soap.sforce.com'>
<sf:username>@{username}</sf:username>
<sf:password>@{password}</sf:password>
</sf:login>
</env:Body>
</env:Envelope>
]]>
</entity>
</http>
<!-- Parse response -->
<xmltask destbuffer="loginResponseBuffer">
<insert path="/">${loginResponse}</insert>
</xmltask>
<if>
<!-- Success? -->
<equals arg1="${loginResponseStatus}" arg2="200"/>
<then>
<!-- Parse sessionId and serverUrl -->
<xmltask sourcebuffer="loginResponseBuffer" failWithoutMatch="true">
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/:loginResponse/:result/:sessionId/text()" property="@{sessionId}"/>
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/:loginResponse/:result/:serverUrl/text()" property="@{serverUrl}"/>
</xmltask>
</then>
<else>
<!-- Parse login error message and fail build -->
<xmltask sourcebuffer="loginResponseBuffer" failWithoutMatch="true">
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='Fault']/*[local-name()='faultstring']/text()" property="faultString"/>
</xmltask>
<fail message="${faultString}"/>
</else>
</if>
</sequential>
</macrodef>
<!-- Base64 encode a files data -->
<macrodef name="base64encode">
<attribute name="file" description="File to base 64 encode."/>
<attribute name="base64" description="Property to store the base 64 encoded data."/>
<sequential>
<loadfile property="filedata" srcFile="@{file}"/>
<script language="javascript">
importClass(javax.xml.bind.DatatypeConverter);
project.setProperty('@{base64}',
DatatypeConverter.printBase64Binary(
new java.lang.String(project.getProperty('filedata')).getBytes("UTF-8")));
</script>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce REST API for a SOQL query -->
<macrodef name="runQuery" description="Run database query">
<attribute name="sessionId" description="Salesforce user name."/>
<attribute name="serverUrl" description="Salesforce url."/>
<attribute name="query" description="Salesforce password."/>
<attribute name="queryResult" description="Query result property name"/>
<sequential>
<!-- Extract host/instance name from the serverUrl returned from the login response -->
<propertyregex property="host"
input="${serverUrl}"
regexp="^((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$"
select="\3"
casesensitive="false" />
<!-- Execute Apex via REST API /query resource -->
<http url="https://${host}/services/data/v29.0/query" method="GET" entityProperty="queryResultResponse" statusProperty="loginResponseStatus" printrequestheaders="false" printresponseheaders="false">
<headers>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<query>
<parameter name="q" value="@{query}"/>
</query>
</http>
<property name="@{queryResult}" value="${queryResultResponse}"/>
</sequential>
</macrodef>
</project>
@imchikachirag
Copy link

Ye sessionId does work in sf:deploy. However if providing a session id. 'serverurl' attribute should be a specific app server instance (e.g. na1, na2) and can't be login.salesforce.com or test.salesforce.com, it needs to be na14.salesforce.com or so.

@afawcett
Copy link
Author

afawcett commented Apr 7, 2015

Thats really interesting, i didn't know sf:deploy supported sessionId cool!

@afawcett
Copy link
Author

afawcett commented Apr 7, 2015

Though i don't see any reference in the Ant migration tool docs?

@imchikachirag
Copy link

I tried it and I'm using it too, it works like a charm :)

<sf:deploy serverurl="${sf.destinationserverurl}" sessionId="${sf.destinationsessionId}" maxPoll="${sf.maxPoll}" deployRoot="exported_foldername" rollbackOnError="true"/>

@arangari
Copy link

arangari commented Oct 5, 2015

Hello! Thanks for this script.

i am using this script for deploying dependency tree before start working. however I am not able to define that the dependent package should be deployed for everyone, instead of only single user.

how would i do that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment