Category: Subversion

Jan 5 2010

A File and Its Method: Planning out and implementing Server Specific Variables in your Applications

Here is the problem...

I want errors to be sent to the screen in development. I want errors sent to me via email in testing and production. I want the "contact us" email address to be mine in development and testing, but the client in production. I want all cfcs to be cached on every request in development (not cached, essentially) but not in testing and production. I can go on if you want, but that's just more reading for you. 

Simply put, I want my application to act slightly different in different environemnts. I didn't even bring up running the application locally.One way is to edit the code directly in each environment (really bad). Another is to have a config file in each environment (not as bad). But if you are like me, working in an enterprise environment, both of those options simply may not be possible.

Due to our change management policies, I have no control over anything after development, so hard coding anything is out of the question. Deploying different files in different environments would be possible but a terrific headache.

Solution One: cf-server.xml

I have in the past deployed an xml file to the root of each server that would handle how applications would handle several basic things. Mostly, when to cache cfcs and handle errors. The problems I had were these:

  • That xml file got overwritten on various deployments  and would end up being incorrect
  • It was very limiting.  It was per server and very few settings
  • I did not give a specific application its own settings. Sometimes I wanted it to handle caching or debugging differently than another (causeing bullet point 1)

Solution Two: Context.cfm and getContext()

Ok, once again I got this idea from Rails. Looking at the database config file where each environment is listed with all the connection information specified. That, of course, would not work in our enterprise environment due to developers not being privy to database login credentials apart from development. But it is a cool idea. Plan up front!. So why not do something like that?

So, first off, and I have said this before, we have our own framework. It's part MVC but not really and part OOP but not really. Very lightweight and pretty pwerful for building apps at warp speed. I am the primary developer managing the framework. So I get to be creative with it.

So this solution is going to be in the next release.  I have a working example and I think it would be pretty easy to implement regardless of how you are building apps. It also only took me about 30-60 minutes to build it in and start using from the point I got the idea. And I am pretty excited about it.

A File and a Method

It basically requires a file and a method in the Application.cfc file.

The idea is to create what I am calling an application context ( I have been using Railo a bit so "context" just popped into my head)

The File

Start by adding a context.cfm file to your app in an appropriate place. For example:

/yourdomain/config/context.cfm

This will will layout your different environments and for each environemnt, the server name and the list of variables you want instantiated.

Here is an example:

~Local
server:localhost
refresh:everytime
wak:/
error:screen
email:hold
status:true
deploy:none

~Testing
server:test
refresh:everytime.multipliedbyinfinity.com
wak:\
error:email
email:hold
status:true
deploy:none

~Prod
server:www.multipliedbyinfinity.com
wak:\
refresh:manual
error:email
email:send
status:false
deploy:manual

 

To give you and idea of what these settings are doing...

  • Wak - windows vs unix (yes I know there are better ways of doing this
  • Refresh - when and if to cache your apps cfcs
  • error - send to the screen with a big ol cfdump or pleasant error message and email big ol cfdump
  • email - send out emails generated by the app or not
  • status - the framework creates a status.cfm file that lets you know what cfcs got created automatically and if there were any errors along with other settings and info. Locally and development, let me just access it. in PPRD and PROD make it password protected or just not accessible
  • deploy: deploy.cfm will export the entire app from svn to the server. make it accessible just in development

You get the idea, right?

The Method

Here is the method that uses this file:

 

<cffunction name="getContext" access="public" returntype="struct" output="false">

    <!--- get the file root so we can cffile the context.cfm file --->
    <cfset fileroot = replaceNoCase(getbasetemplatepath(), "index.cfm", "")>

    <!--- which environment are we in? --->
    <cfset thisServer = cgi.server_name >

    <!--- read the file --->
    <cffile action="read" file="#fileRoot#/myAppLocation/config/context.cfm" variable="config">
   
    <!--- create a sturcture to hold the data --->
    <cfset context = structNew()>

    <!--- loop thorugh with the ~ delimiter  --->
    <cfloop list="#config#" delimiters="~" index = "i">
       
      <cfset server[i][1] = listFirst(i, "
    ")>

     <!--- set the enviroment (local, test, prod..)  --->
    <cfset currentEnv = listFirst(i, "
        ")>
    <!--- set the server of the environment you are looping through --->
    <cfset currentServer = listLast(listgetAt(i,2, "
        "), ":")>

      <!--- if the current server matches the servername in the this file, this is the set of variables that you need to instatiate --->
    <cfif thisServer eq currentServer>
   
    <!--- loop through all the variables and values and set them as elements in the stucture --->  
    <cfloop from="2" to="#listLen(i, "
            ")#" index="s"

         <cfset setting = listGetAt(i, s, "
                ")>   
        <!--- this is the  context.variable = value function--->
        <cfset context[listFirst(setting, ":")] = listLast(setting, ":")>
   
    </cfloop>
        <!--- go ahead and set the file type--->
        <cfset context.fileRoot = fileRoot>

    </cfif>
  
</cfloop>

<cfreturn context>

</cffunction>
Two things, yes I forgot to var scope the variables. on todo list. And not using chr() is sloppy.
 
So now add a call in your OnApplicationStart() method:

application.context = getContext();

Whammo!

You now have a structure in the applicaiton scope called context and you can start using it to decide how your app should work

<cfif application.context.debug eq "screen">
    <cfdump = "#foo#">
<cfelse>
   <cfmail/>
</cfif>

4 comments - Posted by Jonathan at 9:00 PM - Categories: Ruby on Rails | ColdFusion | General | Software | Framwwork | Subversion | Railo

Dec 10 2009
Aug 5 2009

Deploying Subversion Repositories with Coldfusion on Windows Server

We moved our SVN repos off our CF Development box onto it's own server so it can be utilized across departments/development teams. One of the things we lost was using nice little hooks to deploy our apps to the development server on each commit. So I worked up a new method to deploy from subversion. Here's how...

A couple of assumptions:

  1. Development CF Server and SVN Server can talk to each other and the the Development Server can access repos
  2. Development server has the SVN client on it
  3. For this example, we have a database/app where we register our applications (mostly used to control Service DNs and LDAP Authentication but what I used to do this)
  4. CF Development server is running windows

So here are the basics:

Create a Windows .bat file on the Development Server

Create the following deploy.bat file on your CF Dev Server (outside of the root). It will take a handful (4) of arguments.

%1 = webroot folder (we have multiple for different jruns
%2 = Application Directory

%3 = SVN Root (usually 'svn/' but we also have multiple of those
%4 = Repo

I will explain why I break up the svn server and file path later.

----------------------------------------

@echo off
echo make sure the fourth var is 'iws'
echo %4 |FIND "iws" > nul   

echo if not goto exit on error, if so, deploy the repo
if errorlevel=1 goto exitOnError
if errorlevel=1 goto deploy

:deploy

echo start deploy now
echo clear out directory D:\%1\%2
rmdir /S /Q  D:\%1\%2
echo done!

echo begin the deployment
echo exporting from https://mysvn.com/%4/%3/%5 to D:\%1\%2
svn export --force  https://mysvn.com/%4/%3/%5  D:\%1\%2
echo done
goto end

:exitOnError

echo not enough vars
:end

----------------------------------------

Pretty simple.

I am using the export command instead of update to remove all the .svn folders. You have to delete the current files first or you will get errors. Therefore, I check for that specific variable as a hackish way to make sure the "root directory" is not left blank. If it was, i could wipe all subfolders (apps) and that would not be good.

So basically, I break things down and make sure all 4 variables are passed in by checking that the 4th is specifically correct.

Create a CFML Page on development to fire off the deployment

Once you have that bat file ready, you will need to create a way to execute it and pass in the arguments. I have built onto an Application Manager application (redundant,right?) but here is basically what you need:

First Step: Get SVN Information from Subversion

Use <cfexecute> to send the list command to subversion and get back all tags and trunks (assuming you are using trunk/tags/branches). The output from the bat file will be captured as variable="youroutputvariable" (in this case result and branchesresult).

Parse the output and make two lists of tags and branches. Create a form and populate a select box with values.

<!--- get all tags --->
<cfexecute name="svn"
            arguments="list https://yoursvnserver/svn/#qApp.svn_repo#/tags"
            variable="result"
            timeout="60" />

<!--- get all branches --->
<cfexecute name="svn"
            arguments="list https://yoursvnserver.com/svn/#qApp.svn_repo#/branches"
            variable="branchresult"
            timeout="60" />

<!--- create a list delimited by ";" from the returned data --->
<cfset tagList = replace(result, chr(10), ';', "all")>
<cfset branchList = replace(branchresult, chr(10), ';', "all")>

<cfparam name="form.version" default="trunk">

<!--- form to control what is going to be deployed --->
<form method="post">

<!--- list out trunk, all tags then branches as a select box --->
<select name="version">
   
    <option value="trunk">Trunk</option>
   
    <cfloop list="#tagList#" delimiters=";" index="t">
    <cfoutput><option value="tags/#t#">tags/#t#</option></cfoutput>
    </cfloop>       
   
    <cfloop list="#branchList#" delimiters=";" index="b">
    <cfoutput><option value="Branches/#b#">branches/#b#</option></cfoutput>
    </cfloop>

</select>


    <input type="submit" name="submit" value="submit">
    <input type="hidden" name="action" value="deploy">

</form>

Second Step: Process form to call the deploy.bat file and export the repository

As I stated, i have built onto a application manager that stores the actual svn and root path information. I populate that from a query. You could build this into your form and find another way to handle storing the svn information. but here is the code handling the request:

<style >

/* nifty little css scrolling window to hold the dos output */
.dos{overflow:scroll; width:600px; height:300px; border:thin solid #ccc; padding:5px]}
</style>

<cfset qApp =  application.com.appManager.get(id=url.id)>

<!--- if form is blank, default to trunk --->

<cfif form.version eq "">
    <cfset form.version = "trunk">
</cfif>

<!--- set your svn and root information from query --->
    <cfset svnRoot = "#qApp.svn_root#">
    <cfset svnRepo = "#qApp.svn_repo#">
    <cfset jrunDirectory = "#qApp.app_root#">
    <cfset rootDirectory = "#qApp.app_folder#">

    <!--- this is what is submitted ---> 
    <cfset release = "#form.version#">

Starting deployment<br />
<cfflush>

     <!--- call the deploy.bat file and pass in arguments --->  

     <cfexecute 
            name="cmd.exe"
            variable="result"
            timeout="30"
            arguments="/c d:\esapps\deployer\deploy.bat  #jrunDirectory# #rootDirectory# #svnRepo# #svnRoot# #release#" 
            />

     <!--- write out response from dos --->
    <cfoutput>
    <div class="dos">
    <pre>
    #result#
    </pre>
    </div>
    </cfoutput>

<p>done!</p>

There you go. Application is deployed.

5 comments - Posted by Jonathan at 8:49 AM - Categories: Subversion | ColdFusion

Subscribe

Categories

Monthly Archives

Search Archives

Favorite Links

My Links

Coldfusion Links

Recent Tweets