Monday, July 12, 2010

.Net Self Serve Automated deploy

There are a couple of prerequisites that need to be in place in order for a self serve automated deployment system to work.
1. You should have a Nant, MsBuild, Visual Build, or a similar type of scripting file that is doing deploys for you already - see my previous posts on doing that here
2. Your folder structure for every project you want to deploy should be similar. To have a good Self-Serve Deploy tool, it needs to work on the assumption that directory structures are consistent for certain types of projects. This could be set up using your repository tags directory, or automatically copying deployment files to the required folder structure after the build is successful. A folder structure I've used in the past is b:\deployment\projectname\version\ My build file(s).  Any other required folders and files reside in the version folder with a consistent folder structure.
3. You need either a properties file or a DB table to manage which tag/version has been deployed to which environment. We've used a symlink in the past to denote the current 'tip' build. This example will use a file. The file is structured like:

<versions>
   <serverType>
       <server name="blahboxType" version="1.2" />
       <server name="fooboxType" version="1.0" />
   </serverType>
   <blahboxType>
       <project name="MyProject" version="3.1.0.23" />
       <project name="MyProject2" version="3.1.4.11" />
   </blahboxType>
   <fooboxType>
      <project name="fooProj1" version="6.2.123"/>
      <project name="fooProj2" version="6.3.92"/>
   </fooboxType>
</versions>

Once the deploy is finished to a particular environment, logic in the deployment file automatically updates the respective project in the appropriate version file.

Here's a bit of sample code for a client side asp page for a Self Serve app.  It passes some values to a server side asp page (deploy.asp) so that page will know which version of which project to deploy into which environment.  The onchange event of the env select control called an AJAX function that retrieves the currently deployed version of the project from the versions.xml file. 

<form action="deploy.asp" method="get" name="MyProject">
  <input name="project" type="hidden" value="MyProject" />
  <input name="serverType" type="hidden" value="blahboxType" />
  <select name="version">
     <option value="">--Select--</option>
     <option value="" & version & "">" & version & "</option>
  </select>

<select id="MyProject" name="env" onchange="getVersions('MyProject',this.value)">
    <option value="">--Select--</option>
    <option value="Test1">Test 1</option>
    <option value="Test2">Test 2</option>
    <option value="SB">Sandbox</option>
    <option value="STG">Staging</option>
    <option value="PRD">Production</option>
</select>

<div id="MyProjecttarget" style="color: green;">
-</div>

<input type="submit" value="Deploy" />
</form>

Then on my deploy.asp page, the request parameters are set to variables which are then used to dynamically build the path to the deploy file (in this case a visual build file called Deploy-<serverName>-<component>.bld)  This could be a nant, msbuild, or ant file as well, you'd just have to change which executable you're using in the executeString.  The command is then executed in a Windows Shell and if it has a return code of '0' it was successful.  Successful or not, we do some very simple logging to show who tried to deploy what, where, and when.
Here's the code:

 ' need a longer server timeout setting so our deploy can finish
     Server.ScriptTimeout=1200
'instantiate some vars
    env = ""
    version = ""
    project = ""
    severType = ""
   
'set the vars
    set version = Request.queryString("version")
    set env = Request.queryString("env")
    set project = Request.queryString("project")
    set serverType = Request.queryString("serverType")

    'gets the user
    Set WSHNetwork = CreateObject("WScript.Network")
    user = WSHNetwork.UserName

    if version="" then
        Response.Write("Please ensure you chose a version <br/>")
    elseif env="" then
        Response.Write("Please ensure you chose an environment")
    elseif project="" then
        Response.Write("error occured - project required")
    elseif LCase(env)="PRD" then
        Response.Write("We don't want to deploy to Production from this app")
    else

        Response.Write(version & " " & env & " " & project)
'set up a dynamic path here to the deployment file (nant or ant or visual build file)  This string assumes the
'build file is taking a parameter called Env - the environment to deploy to
        execString = "<pathToVisualBuildExecutable>\VisBuildCmd.exe /b " & Chr(34) & version & "\visual build\Deploy-" & serverType & "-" & component & ".bld" & Chr(34) & " Env=" & env

        Response.Write("<br/>" & executeString)

        set oFs = server.createobject("Scripting.FileSystemObject")
            set oTextFile = oFs.OpenTextFile("<pathOfFileToLogTo>\deploylog.txt", 8, True)
   
        Dim WshShell
        Set WshShell = CreateObject("WScript.Shell")
        proc = WshShell.Run(executeString,6,true)

        if proc="0" then
            response.write("<br/><br/><div style='color:green;font-size:16px;'>Deploy of build " & component & " " & version & " to " & env & " as " & user & " was successful")

            dt = now()
            logtext = dt & ":  Deploy of build " & component & " " & version & " to " & env & " was successful" & vbCrLf
            oTextFile.Write logtext
            oTextFile.Close
        else
            response.write("<br/><br/><div style='color:red;font-size:16px;'>Deploy of build " + component + " " + version + " to " + env + " failed using user " + user)
            dt = now()
            logtext = dt & ":  Deploy of build " & component & " " & version & " to " & env & " failed as user " & user & vbCrLf
            oTextFile.Write logtext
            oTextFile.Close
        end if

        response.write("<br/>Deploy times and results are logged at <pathOfFileToLogTo>\deploylog.txt")

        set oTextFile = nothing
        set oFS = nothing
        set WshShell=nothing

    end if
        response.write("<br/><a href='javascript:history.back()'>Back to deploy page<a/>")

No comments: